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()方法,修改了该值。
参考资料
- 高洪岩. Java多线程编程核心技术[M]. 机械工业出版社, 2015
- blog.csdn.net/qq_34337272…
- github.com/CyC2018/CS-…