一、join & interrupt
这俩方法属于线程对象里的方法,属于线程本身的操作。
1.1:join方法
用于等待一个线程的终止,等待期间将会阻塞,直到被等待的线程终止结束。
所以join
可以用来做多任务异步处理,比如还是拿利用CompletableFuture优化程序的执行效率这篇里的第一个例子做优化,这篇文章里使用线程池的future模式进行多任务异步处理,现在使用join改写下:
再来简单贴下这几个方法:
代码块11 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| private String getTop() { try { Thread.sleep(200L); } catch (InterruptedException e) { e.printStackTrace(); } return "顶部banner位"; }
private String getLeft() { try { Thread.sleep(50L); } catch (InterruptedException e) { e.printStackTrace(); } return "左边栏"; }
private String getRight() { try { Thread.sleep(80L); } catch (InterruptedException e) { e.printStackTrace(); } return "右边栏"; }
private String getUser() { try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } return "用户信息"; }
|
然后现在使用简单的线程做异步处理:
代码块21 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public WebModule getWebModuleMsgSimpleAsync() throws ExecutionException, InterruptedException {
WebModule webModule = new WebModule();
Thread topTask = new Thread(() -> webModule.setTop(this.getTop())); Thread leftTask = new Thread(() -> webModule.setLeft(this.getLeft())); Thread rightTask = new Thread(() -> webModule.setRight(this.getRight())); Thread userTask = new Thread(() -> webModule.setUser(this.getUser()));
topTask.start(); leftTask.start(); rightTask.start(); userTask.start();
topTask.join(); leftTask.join(); rightTask.join(); userTask.join();
return webModule; }
|
测试代码:
代码块31 2 3 4 5 6 7 8
| @Test public void testSimpleASync() throws Exception { long start = System.currentTimeMillis(); WebModule module = webHome.getWebModuleMsgSimpleAsync(); System.out.println("通过异步方法获取首页全部信息消耗时间:" + (System.currentTimeMillis() - start) + "ms"); System.out.println("结果为:" + module.toString()); }
|
测试结果:
1 2
| 通过异步方法获取首页全部信息消耗时间:272ms 结果为:top: 顶部banner位; left: 左边栏; right: 右边栏; user: 用户信息
|
比预估的要多72ms,经过后来的测试,发现这72ms耗时发生在线程创建的时候,以及后续线程状态转换带来的消耗,下面等待异步结束的时间约等于200ms,符合预期。
1.2:interrupt方法
用于主动终止一个线程,线程本身调用该方法后,视为已终止状态,join
解除阻塞,下面来用interrupt
和join
来做个实验:
代码块41 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class JoinTest {
private boolean isStop = false;
public static void main(String[] args) throws Exception { JoinTest test = new JoinTest(); Thread loopT = new Thread(test::loopTask); loopT.start();
sleep(2000L); test.setStop(true);
long s = System.currentTimeMillis(); loopT.join(); System.out.println("线程终止后,join阻塞时间为:" + (System.currentTimeMillis() - s)); System.out.println("end~"); }
public void setStop(boolean stop) { isStop = stop; }
public void loopTask() { while (!isStop) { sleep(1000L); System.out.println("loop trigger ~"); } Thread.currentThread().interrupt(); long s = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { int[] a = new int[100]; } System.out.println("线程终止后,逻辑块运行时间:" + (System.currentTimeMillis() - s)); }
public static void sleep(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } }
}
|
执行结果:
1 2 3 4 5
| loop trigger ~ loop trigger ~ 线程终止后,逻辑块运行时间:129 线程终止后,join阻塞时间为:129 end~
|
即便线程被终止了,后面的逻辑也会触发,join
依旧会选择阻塞,直到后续逻辑执行完毕,事实上,大部分任务都可以及时的终止,比如第一个例子,异步出去的任务,最终都会执行完成,线程变为终止状态,join都可以顺利结束,但是反观上例,如果没人及时的设置isStop
的值,程序会一直执行下去,没有终止态,join会无止境的终止下去,这里提一下stop
,线程的stop方法已被官方标记为不建议使用的方法,如果把上例的interrupt
的调用换成stop,来看看其运行结果:
1 2 3 4
| loop trigger ~ loop trigger ~ 线程终止后,join阻塞时间为:0 end~
|
可以看到,线程终止后的后续逻辑均没有触发,等于说stop
是一种很粗暴的终止线程的方式,一旦被stop
,那么里面的业务逻辑将直接断掉,因此官方并不推荐使用该方法来终止线程。
而interrupt,仅仅是对目标线程发送了了一个中断信号(改变了线程的中断状态而已),当目标线程再次通过obj.wait
、thread.sleep
、thread.join
方法进入阻塞状态时,接收到该信号,就会抛出InterruptedException
异常,这时候需要业务方自行处理或者直接抛出,以结束线程阻塞状态(这里需要注意的是被obj.wait
方法阻塞时,抛出该异常需要目标线程再次获得实例对象obj的锁
才行)。
上述三个需要花费时间
的方法均抛出了InterruptedException异常
,针对这些特性,想要完成以下操作就非常方便了:
- 取消
wait方法
等待notify/notifyAll
的处理
- 取消在
sleep方法
指定时间内停止的处理
- 取消
join方法
等待其他线程终止的处理
取消之后所做的处理,取决于需求,可能会终止线程,或者通知用户已取消,或者终止当前处理进入下一个处理阶段。
二、线程状态迁移图
上面的图展示出来的状态太多太杂,原因是它反应的是jvm里线程的状态迁移,如果换算成实际对操作系统层面的线程的影响,状态则少得多,至少无论是TIMED_WAITING
还是BLOCKED
,对应到操作系统的线程身上,那就是阻塞(或称等待,WAIT
),现在就操作系统层面线程状态变化来简化一下上面的状态迁移图:
这里需要说明的是,JVM里的线程本质上就是操作系统里的线程,图1
是告诉你JVM里线程的状态有多少种,其实就是按照各种影响线程的方法(join
、wait
、notify
等)进行了细分,而图2
则展示出了操作系统层面上的线程状态,上面也说过,其实无论是由join
导致的线程阻塞,还是wait
导致的线程阻塞,对于实际的线程影响性质都是一样的,那就是陷入等待。