Java多线程编程7:join()方法和ThreadLocal类

258 阅读5分钟

join()方法使用

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。另外,一个线程需要等待另一个线程也需要用到join()方法。

方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使得当前线程z阻塞,等待线程x销毁后再继续执行线程z后边的代码。

class MyThread extends Thread{
    @Override
    public void run() {
        try {
            int secondVal=(int) (Math.random()*10000);
            System.out.println(secondVal);
            Thread.sleep(secondVal);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            MyThread t=new MyThread();
            t.start();
            t.join();
            System.out.println("当MyThread对象执行完毕后才执行的语句");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

4139
当MyThread对象执行完毕后才执行的语句

也就是说,Main线程在t执行到t.join()语句时阻塞,直到t线程执行完毕后,main线程才继续执行。

join()方法具有使线程排队运行的作用,有类似同步的运行效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”。join()方法的核心逻辑是:

while (isAlive()) {
        wait(0);
    }

在join线程完成后会调用notifyAll()方法,是在JVM实现中调用的。

join()方法遇到interrupt()方法会报错。

join(long)中的参数是设定等待的时间。与sleep(long)不同,join(long)在内部是使用wait(long)来实现的,所以join方法会释放锁,而sleep不释放锁。

ThreadLocal使用

变量值的共享可以使用public static变量的形式,所有线程都使用一个public static变量。JDK中提供的ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据,实现了线程本地存储的功能。

示例代码:

class Tool{
    public static ThreadLocal threadLocal=new ThreadLocal();
}

class ThreadA extends Thread{
    @Override
    public void run() {
        try {
            for(int i=0;i<5;i++){
                Tool.threadLocal.set("ThreadA"+i);
                System.out.println("ThreadA get value: "+Tool.threadLocal.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadB extends Thread{
    @Override
    public void run() {
        try {
            for(int i=0;i<6;i++){
                Tool.threadLocal.set("ThreadB"+i);
                System.out.println("ThreadB get value: "+Tool.threadLocal.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) throws InterruptedException {
        ThreadA a=new ThreadA();
        ThreadB b=new ThreadB();
        a.start();
        b.start();
        for(int i=0;i<4;i++){
            Tool.threadLocal.set("Main"+i);
            System.out.println("Main get value: "+Tool.threadLocal.get());
            Thread.sleep(200);
        }
    }
}

运行结果为:

ThreadA get value: ThreadA0
ThreadB get value: ThreadB0
Main get value: Main0
ThreadA get value: ThreadA1
ThreadB get value: ThreadB1
Main get value: Main1
ThreadA get value: ThreadA2
ThreadB get value: ThreadB2
Main get value: Main2
ThreadA get value: ThreadA3
ThreadB get value: ThreadB3
Main get value: Main3
ThreadB get value: ThreadB4
ThreadA get value: ThreadA4
ThreadB get value: ThreadB5

可以看到,为三个线程设置了不同的i的范围,但是每个线程对应取得的值是对应的。说明ThreadLocal类解决了变量在不同线程间的隔离性,不同线程中的值可以放入ThreadLocal类中进行保存。

此外,还可以设置initialValue()来设置初始值,使得get()不会返回null。

public class ThreadLocalExt extends ThreadLocal {
        @Override
        protected Object initialValue() {
            return "我是默认值 第一次get不再为null";
        }
    }

每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象。当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal->value 键值对插入到该 Map 中。ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。

在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。

InheritableThreadLocal

使用类InheritableThreadLocal可以在子线程中取得父线程继承下来的值。

class InheritableThreadLocalExt extends InheritableThreadLocal{
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }

    @Override
    protected Object childValue(Object parentValue) {
        return parentValue+" tail";
    }
}

class Tools{
    public static InheritableThreadLocalExt t1=new InheritableThreadLocalExt();
}

class ThreadA extends  Thread{
    @Override
    public void run() {
        try {
            for(int i=0;i<5;i++){
                System.out.println("在ThreadA中取值:"+Tools.t1.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            for(int i=0;i<5;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中取值: 1544438516856
在Main中取值: 1544438516856
在Main中取值: 1544438516856
在Main中取值: 1544438516856
在Main中取值: 1544438516856
在ThreadA中取值:1544438516856 tail
在ThreadA中取值:1544438516856 tail
在ThreadA中取值:1544438516856 tail
在ThreadA中取值:1544438516856 tail
在ThreadA中取值:1544438516856 tail

可以看到,在Main线程中调用了ThreadA线程,ThreadA线程成功取到了Main线程中的值,同时还通过childValue()方法,修改了该值。

参考资料