五、Worker - Thread 模式
Worker Thread 模式,对应到现实世界,类似工厂中的工人做任务,当有任务的时候,工人取出任务执行。
解决的办法是使用线程池,并且使用一个阻塞队列来存储任务,线程池中的线程从队列中取出任务执行。线程池的使用需要注意几点:
- 任务队列尽量使用有界队列,避免任务过多造成 OOM。
- 应该明确指定拒绝策略,可以根据实际情况实现 RejectedExecutionHandler 接口自定义拒绝策略。
- 应该给线程指定一个有意义的名字,最好和业务相关。
- 为不同的任务创建不同的线程池,这样能够有效的避免死锁问题。
六、Two - Phase Termination 模式
1. 两阶段终止概念
Two - Phase Termination,即两阶段终止,主要是为解决如何正确的终止一个线程,这里说的是一个线程终止另一个线程,而不是线程终止自己。
Java 中的线程提供了一个 stop() 方法用来终止线程,这不过这个方法会直接将线程杀死,风险太高,并且这个方法已经被标记为废弃,不建议使用了。
两阶段终止,即将线程的结束分为了两个阶段,第一个阶段是一个线程 T1 向另一个线程 T2 发送终止指令,第二个阶段是线程 T2 响应终止指令。
根据 Java 的线程状态,线程如果要进入 TERMINATED 状态则必须先进入 RUNNABLE 状态,而处于 RUNNABLE 状态的线程有可能转换到休眠状态。
Java 的线程提供了 interrupt() 方法,这个方法的作用便是将线程的状态从休眠状态转换到 RUNNABLE 状态。
切换到 RUNNABLE 状态之后,线程有两种方式可以终止,一是执行完 run() 方法,自动进入终止状态;二是设置一个标志,线程如果检测到这个标志,则退出 run() 方法,这就是两阶段终止的响应终止指令。
2. 程序示例
下面是一个简单的使用 interrupt() 方法和中断标志位来终止线程的示例:
public class Test { public static void main(String[] args) { Thread thread = new Thread(() -> { //检测到中断则退出 while (!Thread.currentThread().isInterrupted()){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); //重新设置中断标志 Thread.currentThread().interrupt(); } System.out.println("I am roseduan"); } }); thread.start(); thread.interrupt(); } }
程序要每隔三秒打印语句,但是线程启动之后就直接调用了 interrupt() 方法,所以线程直接退出了。需要注意的是这里在捕获异常之后,需要重新设置线程的中断状态,因为 JVM 的异常处理会清除线程的中断状态。
在实际的生产中,并不推荐使用这种方式,因为在 Thread 内部可能会调用其他的方法,而其他的方法并不能够保证正确的处理了线程中断,解决的办法便是自定义一个线程的中断标志,如下所示:
public class Test { //自定义中断标志 private volatile boolean isTerminated = false; private Thread thread; public synchronized void start(){ thread = new Thread(() -> { //检测到中断则退出 while (!isTerminated) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); //重新设置中断状态 Thread.currentThread().interrupt(); } System.out.println("I am roseduan"); } isTerminated = false; }); thread.start(); } //线程终止方法 public synchronized void stop(){ isTerminated = true; thread.interrupt(); } }
3. 终止线程池
Java 中并不太会显式的创建和终止一个线程,使用更多的是线程池。
Java 中的线程池提供了两个方法来终止,分别是 shutdown() 和 shutdownNow() ,两个方法的区别如下:
- shutdown():拒绝新的任务,等待正在执行的和已经在阻塞队列中的任务执行完后,再关闭线程池
- shutdownNow():直接关闭线程池,拒绝新的任务,并且中断正在执行的任务,已经在阻塞队列中的任务也不会被执行了。
七、Producer - Consumer 模式
这是较为常用的生产者 - 消费者模式,Java 中的线程池就使用了这种模式,线程的使用方是生产者,提供任务,线程池本身是消费者,取出并执行任务。
生产者 - 消费者模式使用了一个任务队列,生产者将任务添加到队列中,消费者从队列中取出任务执行。
这样的设计的目的有三个:
- 解耦,生产者和消费者之间没有直接的关联,而是通过队列进行通信。
- 其次可以实现异步,例如生产者可以不用管消费者的行为,直接将任务添加到队列中。消费者也可以不在乎生产者,直接从队列中取任务。
- 最后,可以平衡生产者和消费者之间的速度差异。
下面是一个简单的生产者 - 消费者程序示例:
public class ProducerConsumerTest { private BlockingQueue<Task> queue = new LinkedBlockingQueue<>(100); public void produce() { queue.add(new Task()); } public void consume() { Task task = queue.poll(); while (task != null){ task.execute(); task = queue.poll(); } System.out.println("没有任务了"); } public static void main(String[] args) throws InterruptedException { Test test = new Test(); //生产者线程,创建10个任务 Thread producer = new Thread(() -> { for (int i = 0; i < 10; i++) { test.produce(); } });