在上篇文章中,我讲解了关于并发主题的一些基础知识,使大家对于线程有了一个初步的概念。那么今天我就来为大家讲解一下线程之间是如何通信以及它们是怎样协作的。
1.Thread.join()方法
想象一下有两个线程A、B,线程A需要等待线程B执行完毕后才能继续执行,那么此时就可以用join方法来实现。
1 public class JoinThread {
2
3 public static void main(String[] args) {
4 System.out.println("main thread,continue when join thread finish.........");
5 try {
6 NeedJoinThread nj = new NeedJoinThread();
7 nj.start();
8 nj.join();
9 } catch (InterruptedException e) {
10 System.out.println("catch exception in main thread.........");
11 }
12 System.out.println("main thread,join thread is finish, i can continue my job........");
13 }
14 }
15
16 class NeedJoinThread extends Thread{
17
18 @Override
19 public void run() {
20 try {
21 Thread.sleep(10000L);
22 System.out.println("i'm join thread, i'm finish my job..........");
23 } catch (InterruptedException e) {
24 e.printStackTrace();
25 }
26 }
27 }
从上面的代码可以看出,模拟了NeedJoinThread执行任务需要10s,而在主线程中调用了 nj.join() 后,就会让 NeedJoinThread 先执行,主线程会等待其执行完毕后再继续执行后续代码。
看一下Join方法的源码:
1 public final synchronized void join(long millis)
2 throws InterruptedException {
3 long base = System.currentTimeMillis();
4 long now = 0;
5
6 if (millis < 0) {
7 throw new IllegalArgumentException("timeout value is negative");
8 }
9
10 if (millis == 0) {
11 while (isAlive()) {
12 wait(0);
13 }
14 } else {
15 while (isAlive()) {
16 long delay = millis - now;
17 if (delay <= 0) {
18 break;
19 }
20 wait(delay);
21 now = System.currentTimeMillis() - base;
22 }
23 }
24 }
可以看到底层其实就是wait-notify机制(后面会讲)。这里有一个参数millis,指的是设置超时时间。如果没传这个参数,那么当前线程会无限等待,直到join进来的线程执行完毕调用notify唤醒当前线程后,当前线程才会继续执行任务;
否则传了这个参数,当前线程就会在最多等待相应的millis毫秒后继续执行任务,而不再管join进来的线程是否已执行完毕任务。
2.Thread.yield()方法
想象一下A、B两个线程,如果在A执行过程中需要让B线程也有执行机会,但对两个线程的执行顺序并无严格要求的情况下,可以使用Thread.yield()方法。
1 public class YieldThread {
2
3 public static void main(String[] args) {
4 NeedYieldThread ny = new NeedYieldThread();
5 ny.start();
6 }
7 }
8
9 class NeedYieldThread extends Thread{
10
11 @Override
12 public void run() {
13 long beginTime = System.currentTimeMillis();
14 int count = 0;
15 for (int i = 0; i < 1000000; i++)
16 {
17 Thread.yield();
18 count = count + i + 1;
19 }
20 long endTime = System.currentTimeMillis();
21 System.out.println("Cost:" + (endTime - beginTime) + "millisecond!");
22 }
23 }
可以多运行几次这段代码,会发现每次输出的结果都是不一样的。这证明了调用了yield方法的线程会让出CPU执行权,但是让出的时间是不确定的。有可能刚刚让出执行权马上又抢到,也有可能让出好一会儿后才抢到执行权。
3.Thread.sleep(millinSeconds)方法
当需要让一个正在运行的线程暂停一段时间后再继续执行,那么就可以使用Thread.sleep(millinSeconds)这个方法。
1 public class SleepThread {
2
3 public static void main(String[] args) {
4 try {
5 System.out.println("main thread start........");
6 Thread.sleep( 5000L);
7 System.out.println("after five senconds, main thread end.......");
8 } catch (InterruptedException e) {
9 System.out.println("catch exception.........");
10 }
11 }
12 }
主线程会暂停5秒,然后打印end。
注意JDK源码中sleep方法的注释有这么一句话“The thread does not lose ownership of any monitors”,意思就是调用sleep方法的线程不会让出锁的拥有权,只会让出CPU执行权,这一点要牢记。
4.interrupt、interrupted、isInterrupted的区别
上一篇文章已经介绍过了中断机制,那么java的api中有这三个方法并且它们长得很相似,下面就讲一下它们各自的作用:
(1)interrupt方法:对调用该方法的线程设置中断标识。注意,该方法只会打上中断标识,并不是强行让当前线程终止。具体什么时候终止是由线程自己决定的,无法进行人为干预。
(2)interrupted方法:检测当前线程是否已经中断,并且会清除中断标识。因为这个特性,所以当线程调用该方法之后如果再一次调用该方法,那么结果必定为false(因为第一次调用已经清除了中断标识)
(3)isInterrupted方法:同样用来检测当前线程是否已经中断,唯一和interrupted方法不同的是该方法不会清除中断标识。
线程间的基本通讯方式大概就这些,下篇文章会讲解线程间协作非常经典的方式—— wait-notify机制。