Join的使用
在很多情况下,主线程创建并启动子线程,如果子线程中进行大量的耗时运算,主线程往往将早于子线程结束之前结束,这是如果主线程想等待子线程执行玩之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
join方法用来解决的问题
- 方法jion的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无期限的阻塞,等待线程x销毁后再继续执行线程z后面的代码
- 方法join具有使线程排队运行的作用,有些类似同步的运行效果。
public class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
int seconValue=(int)(Math.random()*10000);
System.out.println("secondValue:"+seconValue);
Thread.sleep(seconValue);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
try {
MyThread thread =new MyThread();
thread.start();
thread.join();
System.out.println("我想当thread对象执行完毕后我在执行,我做到了。");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行结果
- secondValue:3846
- 我想当thread对象执行完毕后我在执行,我做到了。
join与synchronized的区别
- join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”原理作为同步。
join与异常
- 在join过程中,如果当前线程对象被中断,则当前线程出现异常
join(long)与sleep(long)的区别
- 方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
- 方法join(long)的源码如下:
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
//获取当前时间的毫秒数
long base = System.currentTimeMillis();
long now = 0;
//当传来的毫秒数小于0时,抛出IllegalArgumentException异常
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
- 从源码中可以了解到,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。
- 而Thread.sleep(long)方法却不释放锁。
- 创建项目join_sleep_1
- 类TreadA.java
public class ThreadA extends Thread{
private ThreadB b;
public ThreadA(ThreadB b) {
this.b = b;
}
@Override
public void run() {
super.run();
try {
synchronized (b){
b.start();
Thread.sleep(6000);
//ThreadB.sleep();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 类ThreadB.java
public class ThreadB extends Thread{
@Override
public void run() {
super.run();
try {
System.out.println(" b run begin timer="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(" b run end timer="+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void bService(){
System.out.println("打印了bService timer="+System.currentTimeMillis());
}
}
- 类ThreadC.java
public class ThreadC extends Thread{
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
super.run();
threadB.bService();
}
}
- 测试类Run.java
public class Run {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
Thread.sleep(1000);
ThreadC c = new ThreadC(b);
c.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出结果
- b run begin timer=1495334781843
- b run end timer=1495334786844
- 打印了bService timer=1495334787843
由于线程ThreadA使用Thread.sleep(long)方法一直持有ThreadB对象的锁,时间达到6秒,所以线程ThreadC只有在ThreadA时间到达6秒后释放ThreadB的锁时,才可以调用ThreadB中的同步方法synchronized public void bService().
- 以上实验证明Thread.sleep(long)方法不释放锁
- 下面继续,验证join()方法释放锁的特点。
- 创建项目join_sleep_2,将项目1中的代码赋值到项目2,更改ThreadA.java代码如下
public class ThreadA extends Thread{
private ThreadB b;
public ThreadA(ThreadB b) {
this.b = b;
}
@Override
public void run() {
super.run();
try {
synchronized (b){
b.start();
b.join();//说明join释放锁了!
for(int i=0;i<Integer.MAX_VALUE;i++){
String newString = new String();
Math.random();
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出结果
- b run begin timer=1495335185878
- 打印了bService timer=1495335186892
- b run end timer=1495335190879
由于线程ThreadA释放了ThreadB的锁,所以线程ThreadC可以调用ThreadB中的同步方法synchronized public void bService().
方法join()后面的代码提前运行:出现意外
针对前面章节中的代码进行测试的过程中,还可以延伸出“陷阱式”的结果,如果稍加不注意就会掉进“陷阱”里。
类ThreadLocal的使用
变量值的共享可以使用public static 变量的形式,所有的线程都使用同一个public static 变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供了类ThreadLocal正式为了解决这样的问题。
- 类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。
- 该类提供了线程局部变量。这些变量不同于它们的普通对应物,因为访问一个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的私有静态字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
方法get()与null
public class Run {
public static ThreadLocal t1 = new InheritableThreadLocal();
public static void main(String[] args) {
if(t1.get()==null){
System.out.println("从未放过值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
输出结果:
从未放过值
我的值
我的值
从结果中可以看出,第一次调用t1对象的get()方法时返回的值是null,通过调用set()方法赋值后顺利取出值并打印在控制台上。
类ThreadLocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程中可以放入Threadlocal类中进行保存的。
验证线程变量的隔离性
- 创建项目ThreadLocalTest
- 类Tools.java
public class Tools {
public static ThreadLocal t1 = new InheritableThreadLocal();
}
- 两个线程类 ThreadA.java
public class ThreadA extends Thread{
@Override
public void run() {
super.run();
try {
for(int i=0;i<100;i++){
Tools.t1.set("ThreadA"+(i+1));
System.out.println("ThreadA get Value="+Tools.t1.get());
Thread.sleep(200);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 类ThreadB.java
public class ThreadB extends Thread{
@Override
public void run() {
super.run();
try {
for(int i=0;i<100;i++){
Tools.t1.set("ThreadB"+(i+1));
System.out.println("ThreadB get Value="+Tools.t1.get());
Thread.sleep(200);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- Run.java
public class Run {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
for(int i=0;i<100;i++){
Tools.t1.set("Main"+(i+1));
System.out.println("Main get Value="+Tools.t1.get());
Thread.sleep(200);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出结果:
Main get Value=Main1
ThreadA get Value=ThreadA1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA2
Main get Value=Main2
ThreadB get Value=ThreadB2
ThreadB get Value=ThreadB3
ThreadA get Value=ThreadA3
Main get Value=Main3
结果显示,虽然3个线程都在向t1对象中set()数据值,但每个线程还是能取出自己的数据
再次验证线程变量的隔离性
- 创建项目ThreadLocal33,类Tools.java代码如下
public class Tools {
public static ThreadLocalExt t1 = new ThreadLocalExt();
}
- 类ThreadLocalExt.java
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
- 类ThreadA.java
public class ThreadA extends Thread {
@Override
public void run() {
super.run();
try {
for(int i=0;i<10;i++){
System.out.println("在ThreadA线程中取值="+ Tools.t1.get());
Thread.sleep(100);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 测试类Run.java
public class Run {
public static void main(String[] args) {
try {
for(int i=0;i<10;i++){
System.out.println(" 在Main线程中取值="+ Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出结果:
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在Main线程中取值=1495345010495
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
- 在ThreadA线程中取值=1495345016541
结果展示出:子线程和父线程各有各自所拥有的值
类InheritableThreadLocal的使用
- 使用InheritableThreadLocal可以在子类中取得父线程继承下来的值
值继承
- 使用InheritableThreadLocal类可以让子线程从父线程中取得值
- 创建测试用的项目InheritableThreadLocal1,类InheritableThreadLacalExt.java
public class InheritableThreadExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
- 类Tools.java
public class Tools {
public static InheritableThreadExt t1 = new InheritableThreadExt();
}
- 线程ThreadA.java
public class ThreadA extends Thread{
@Override
public void run() {
super.run();
try {
for(int i=0;i<10;i++){
System.out.println("在ThreadA线程中取值 ="+ Tools.t1.get());
Thread.sleep(100);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 测试类Run.java
public class Run {
public static void main(String[] args) {
try {
for(int i=0;i<10;i++){
System.out.println(" 在Main线程中取值 ="+ Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出结果:
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在Main线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
- 在ThreadA线程中取值 =1495349571111
值继承再修改
如果在继承的同时还可以对值进行进一步的处理那就更好了。
- 再上一个项目之上进行修改,更改类InheritableThreadLocalExt.java
public class InheritableThreadExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
@Override
protected Object childValue(Object parentValue) {
return parentValue+" 我在子线程加的~!";
}
}
输出结果:
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在Main线程中取值 =1495350012034
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 在ThreadA线程中取值 =1495350012034 我在子线程加的~!
- 但在使用InheritableThreadLocal类需要注意一点的是,如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值。