一、如何实现多线程
-
实现 Runnable 接口
public static void main(String[] args) { MyThread myThread = new MyThread();// 一个实现了Runnable接口的类 Thread t = new Thread(myThread);// 声明一个线程 t.start();// 启动线程 } public class MyThread implements Runnable { @Override public void run() {// 启动线程后执行的方法 System.out.print("run the \"run()\" method!"); } }
-
继承 Thread 类
public static void main(String[] args) { MyThread myThread = new MyThread();// 一个继承了Thread类的类 myThread.start();// 启动线程 } public class extends Thread { @Override public void run() {// 启动线程后执行的方法 System.out.print("run the \"run()\" method!"); } }
-
使用 ExecutorService、Future 和 Callable 创建有返回值的线程
public static void main(String[] args) { // 创建一个ExecutorService ExecutorService executorService = Executors.newCachedThreadPool(); // new一个MyThread线程,并交给executorService执行, // 通过Future接收返回结果 Future future = executorService.submit(new MyThread()); try { // 从future中获取返回值 Object result = future.get(); System.out.println(result.toString()); } catch (Exception e) { e.printStackTrace(); } } // 一个有返回值的线程 public class MyThread implements Callable { @Override public String call() { System.out.print("run the \"call()\" method!"); return "test"; } }
二、实质
- Runnable、Callable 接口和多线程的实现没有关系
接口的作用是约束行为,Runnable 接口的作用是指定一个协议,规定所有继承这些接口的类都要有一个无参无返回值的方法- 多线程的实现是由类来完成的
java 中所有的多线程最终都通过 Thread 类来实现,线程的资源分配、线程启动这些工作都是在 start()方法中完成的,严格来讲,在 start0()方法中完成的。start0()是一个 native 方法,并不是用 java 语言实现的。
- 可以通过两种方式来定义多线程中要完成的工作
- 实现 Runnable 接口
Runnable 接口中的 run()方法,没有返回值 - 实现 Callable 接口
Callable 接口中的 call()方法,有返回值
- 实现 Runnable 接口
- 需要对线程进行管理(提交、启动、终止)
- 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()方法可以用来停止线程池
- Thread 类
三、详解
- 线程的生命周期
- 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 状态
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于