五 . 方法join#
1. 简单使用#
很多情况下,主线程中启动子线程,然后两条线程并行运行,主线程往往早于子线程之前结束,那么,假如说主线程想等子线程执行完毕后,拿到子线程的结果后再结束,那么最直接的方法就是 使用join()
public class myjoin extends Thread{ public void run(){ System.out.println("我是子线程,我要睡两秒..."); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { try { myjoin myjoin = new myjoin(); myjoin.start(); myjoin.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"我成功的等待子线程执行完后,执行"); }
运行结果
我是子线程,我要睡两秒... main我成功的等待子线程执行完后,执行
- join方法的作用就是,执行当前线程的run()中的任务,直到任务结束,线程对象销毁后,才执行后面的代码
2. join方法与异常#
- 当join()所在的线程碰到了interrupted()会摩擦出怎样的火花呢?
观看如下代码
public class ThreadA extends Thread { public void run(){ try { System.out.println("ThreadA 执行了,紧接着睡五秒..."); Thread.sleep(5000); System.out.println("五秒了, ThreadA 醒过来..."); } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadB extends Thread{ public void run(){ try{ System.out.println("ThreadB 启动了..."); ThreadA threadA = new ThreadA(); threadA.start(); threadA.join(); System.out.println("ThreadA join 之后的代码..."); }catch (Exception e){ System.out.println("ThreadB catch块打印了..."); e.printStackTrace(); } } } public class ThreadC extends Thread { Thread thread; public ThreadC(Thread b){ this.thread=b; } public void run(){ thread.interrupt(); } public static void main(String[] args) { ThreadB threadB = new ThreadB(); threadB.start(); ThreadC threadC = new ThreadC(threadB); threadC.start(); System.out.println("主线程结束..."); } }
运行结果:
ThreadB 启动了... 主线程结束... java.lang.InterruptedException ThreadB 启动了... ThreadB catch块打印了... ThreadA 执行了,紧接着睡五秒... at java.lang.Object.wait(Native Method) at java.lang.Thread.join(Thread.java:1252) at java.lang.Thread.join(Thread.java:1326) at com.atGongDa.MultiThreading.线程之间的通信.ThreadB.run(ThreadB.java:10) 五秒了, ThreadA 醒过来...
- 通过出现了中断异常,原因是ThreadA还在运行,并且没出现异常
- System.out.println("ThreadA join 之后的代码..."); 并未输出, ThreadB确实被中断了
- ThreadA正常执行结束
当时对join()阻塞的是哪条线程的代码还是有点模糊,现在分析结果,可以看到,join阻塞的是join()方法 所在的那条线程,根据上面的例子,join只能阻塞ThreadB,却不能阻塞ThreadC 和 主线程
3. 意外: join()后面的代码提前执行现象与解释#
运行下面的代码:
public class ThreadQ extends Thread{ private Thread W; public ThreadQ(Thread thread) { this.W = thread; } public void run(){ synchronized (W){ System.out.println("ThreadQ准备开始睡三秒..."+System.currentTimeMillis()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("ThreadQ...睡醒了..."+System.currentTimeMillis()); } } } public class ThreadW extends Thread { synchronized public void run() { try { System.out.println("ThreadW 启动了...要睡三秒"+System.currentTimeMillis()); Thread.sleep(3000); System.out.println("ThreadW睡醒了..."+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadE { public static void main(String[] args) { ThreadW w = new ThreadW(); ThreadQ threadQ = new ThreadQ(w); w.start(); threadQ.start(); try { threadQ.join(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main end..."); } }
运行结果: 可以看到,main end提前打印出来了
ThreadW 启动了...要睡三秒1549362922247 main end... ThreadW睡醒了...1549362925248 ThreadQ准备开始睡三秒...1549362925248 ThreadQ...睡醒了...1549362928248
当我们把join(2000)注释掉后结果如下
main end...1549363120715 ThreadQ准备开始睡三秒...1549363120715 ThreadQ...睡醒了...1549363123715 ThreadW 启动了...要睡三秒1549363123715 ThreadW睡醒了...1549363126716
分析结果,不难看出,在线程的启动一条新的线程比它运行自己的代码要快的多.因此,大多数情况下,都是join()方法先执行,拿到对象锁,然后马上释放掉...,然后Q抢到ThreadW对象锁,睡上三秒且不释放,ThreadW因为没有锁,故执行不了自己加上了synchronize的run()方法,本来join()可以阻塞Zhu后面的代码,可是join(2000),发现自己已经过期了,因此ThreadW和ThreadMain就会异步执行
六. join() & sleep()的区别#
- join()方法底层是wait()实现的,这也就意味着,当我们调用join()方法时,它会做两件事
- 阻塞join()方法所在的线程1,执行调用join()方法的线程2的run任务
- 释放掉调用join方法的线程2对象的 对象锁
这也就意味着,其他的线程可以访问调用join方法的线程2的同步方法...
- sleep()不会释放,运行当前代码的线程对象的 对象锁,也就是说,其他线程是不能访问此线程的其他同步方法的
七 ThreadLocal#
变量值的共享可以使用public static 修饰,所有的线程都使用同一个public static 的变量,如果想实现每一个线程都有自己的共享变量呢? ThreadLocal ,可以把它当成一个专属于当前线程对象的盒子,它保证了线程之前的隔离性
- 当我们在run方法中,直接new ThreadLocal对象的时候,get()出来的默认值为null
重写 initialValue值设置默认值
@Override protected Object initialValue() { return "123"; }
八 InheritableThreadLocal#
- 共用一套工具InheritableThreadLocal,实现了让子线程从父线程中获取值,
重写childValue方法,实现继承值的修改,
@Override protected Object childValue() { return "XXX"; }
注意:
当子线程从父线程中获取值的同时,父线程把值修改了,子线程获取到的值为旧值
在java jdk1.5开始,java平台提供了更高级的并发工具,他可以完成以前必须在wait()和notify()上手写代码来完成的各项工作,这在一定程度上让我们几乎没有任何理由再去使用wait和notify,这也是<>提及的第69条,并发工具1.Executor Framework 2,Concurrent Collection 3同步器 Synchronizer ,优先于wait notify
参考书籍<<java多线程编程核心技术>>高洪岩著 <>