一、如何实现多线程
1 public static void main(String[] args) {
2 MyThread myThread = new MyThread();// 一个实现了Runnable接口的类
3 Thread t = new Thread(myThread);// 声明一个线程
4 t.start();// 启动线程
5 }
6
7 public class MyThread implements Runnable {
8 @Override
9 public void run() {// 启动线程后执行的方法
10 System.out.print("run the "run()" method!");
11 }
12 }
1 public static void main(String[] args) {
2 MyThread myThread = new MyThread();// 一个继承了Thread类的类
3 myThread.start();// 启动线程
4 }
5
6 public class extends Thread {
7 @Override
8 public void run() {// 启动线程后执行的方法
9 System.out.print("run the "run()" method!");
10 }
11 }
使用 ExecutorService、Future 和 Callable 创建有返回值的线程
1 public static void main(String[] args) {
2 // 创建一个ExecutorService
3 ExecutorService executorService =
4 Executors.newCachedThreadPool();
5 // new一个MyThread线程,并交给executorService执行,
6 // 通过Future接收返回结果
7 Future future =
8 executorService.submit(new MyThread());
9 try {
10 // 从future中获取返回值
11 Object result = future.get();
12 System.out.println(result.toString());
13 } catch (Exception e) {
14 e.printStackTrace();
15 }
16 }
17
18 // 一个有返回值的线程
19 public class MyThread implements Callable {
20 @Override
21 public String call() {
22 System.out.print("run the "call()" method!");
23 return "test";
24 }
25 }
二、实质
1 Runnable、Callable 接口和多线程的实现没有关系
2 接口的作用是约束行为,Runnable 接口的作用是指定一个协议, 规定所有继承这些接口的类都要有一个无参无返回值的方法
3 多线程的实现是由类来完成的
4 java 中所有的多线程最终都通过 Thread 类来实现, 线程的资源分配、线程启动这些工作都是在 start()方法中完成的, 严格来讲, 在 start0() 方法中完成的。start0() 是一个 native 方法, 并不是用 java 语言实现的。
可以通过两种方式来定义多线程中要完成的工作
- 实现 Runnable 接口
Runnable 接口中的 run() 方法, 没有返回值
- 实现 Callable 接口
Callable 接口中的 call() 方法, 有返回值
需要对线程进行管理 (提交、启动、终止)
- Thread 类
- Thread 类有一个构造函数可以传递一个 Runnable 类型的参数 target, 可以通过 target 来提交想要执行的任务 (实现 run() 方法, 然后传给 Thread 实例)
- Thread 类本身实现了 Runnable 接口, 也可以直接通过实现 Thread 类来提交想要执行的任务 (重写 run() 方法)
- Thread 类的 start()方法负责启动执行线程, 当 start() 方法执行时
- 若已经重写了 run() 方法来执行任务, 则会执行该方法
- 若传入了 target 参数, 则会调用 target 中的 run() 方法
- Thread 类中有 stop 方法负责停止线程, 但是已经弃用
- 可以通过 interrupt() 方法中断线程
- ExecutorService 接口
- 可以通过 executor()方法或 submit() 方法提交任务
- submit() 方法提交的任务会返回一个 Future 对象, 可以通过这个对象来获取返回结果
- shutdown()和 shutdownNow() 方法可以用来停止线程池
三、详解
线程的生命周期
- new thread(新建):创建一个线程实例, 比如通过 new 操作创建一个 Thread 类的实例, 此时线程未被启动
- runnable(可运行):一个线程创建好之后, 需要通知 cpu 这个线程可以开始执行了, 比如 thread 类的 start() 方法执行后, 此时线程在就绪队列中等待 cpu 分配资源
- running(运行中):线程获得 cpu 资源后开始运行, 比如运行 run() 方法中的逻辑, 此时除非线程自动放弃 cpu 资源或者有优先级更高的线程进入, 否则将执行到线程结束
- dead(死亡):线程正常执行结束, 或者被 kill 调, 此时线程将不会再次被执行
- block(阻塞):线程主动让出 cpu 使用权、其它更高优先级的线程进入、该线程的时间片用完,但此时该线程还没有执行完成, 都会使线程进入 block 状态, 进入 block 状态的线程还可以回到就绪队列中等待再次执行。
Thread 类中的方法
- start:启动一个线程, 这个方法会是线程进入 Runnable 状态, 等待执行
- isAlive:判断线程是否处于活动状态(Runnable 或 running)
- sleep:强制让线程放弃当前时间片进入休眠状态一定时间, 此时线程会进入 block 状态, 直到休眠的时间结束, 再进入 Runnable 状态。sleep 是静态方法, 只能控制所在线程。sleep(0) 会直接触发下一次 cpu 竞争, 如果没有优先级更高的线程, 则会继续工作
- wait(override Object):放弃对象锁, 进入等待池, 只有针对此对象调用 notify() 方法之后, 才会再次进入 Runnable 状态
- join:阻塞等待线程结束, 可以接收参数 millis 和 nanos 指定等待的最大时间
- interrupt: 中断线程, 这个方法并不能中断正在运行的线程, 运行该方法后, 只有当线程被 join(),sleep() 和 wait() 方法所阻塞时, 才会被 interrupted 方法所中断, 并抛出一个 InterruptedException 异常
- static yield:主动放弃 cpu 使用权, 回到 Runnable 状态
四、Tips
- block 状态
- 等待阻塞:运行线程执行了 wait 方法, 该线程会释放占用的所有资源包括对象锁, 进入等待队列中。进入等在队列的线程是不能自动唤醒的, 必须依靠其它线程调用 notify()、notifyAll() 来进行唤醒(该状态下线程会释放对象锁)
- 同步阻塞:运行的线程在获取对象同步锁时, 同步锁已被其它线程占用, 则该线程会进入锁队列等待获取同步锁, 直到获取到同步锁之后回再次进入 Runnable 状态(该状态下线程还没有获得对象锁)
- 其它阻塞:运行的线程调用了 sleep()或 join() 方法, 或者发出 IO 请求, 该线程会进入阻塞状态, 知道 sleep 超时、join 所等待的线程结束或是 IO 操作完成, 则会再次进入 Runnable 状态(该状态下线程只会放弃 cpu 而不会释放对象锁)
- sleep(0)
sleep(0) 会重新触发一次 cpu 竞争, 当 Runable 队列中有大于或等于当前线程优先级的线程时, 当前线程会进入 Runnable 队列将 cpu 的使用权让出, 否则会继续运行
- sleep()和 wait()
- sleep 方法会让出 cpu, 但不会释放对象锁, 等到 sleep 超时之后会自动进入 Runnable 队列
- wait 方法会让出 cpu, 并释放对象锁, 需要其它线程调用 notify()、notifyAll() 才能重新进入 Runnable 队列
- interrupt()
interrupt 方法的作用更倾向于告诉线程, 你可以结束了, 而不是直接地中断线程, 知道线程进入阻塞状态时, 才能中断线程。对于陷入死循环、IO 等待等难以进入阻塞状态的线程来说,interrupt 方法是不能有效中断的。
- sleep()和 yield()
这两个方法都会让出 cpu 使用权,sleep 会进入 block 状态, 而 yield 会直接进入 Runnable 状态
本文出自:hacpai:Zl992532172