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.
     *
     * <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中的值进行更改,那么子线程取到的值还是旧值。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容