Join、ThreadLocal、InheritableThreadLocal方法的使用

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.
     *
     * 

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 * interrupted status 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

输出结果

  • 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中的值进行更改,那么子线程取到的值还是旧值。

你可能感兴趣的:(Join、ThreadLocal、InheritableThreadLocal方法的使用)