join()方法和TheadLocal类

Thread.join()

使用方式

1
2
3
Thread t = new AThread();
t.start();
t.join();

为什么要用join()方法

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

join方法的作用

JDK中对join方法解释为:“等待该线程终止”,换句话说就是:”当前线程等待子线程的终止“。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了当前线程才能执行。

用实例来理解

  1. 简单了解join()的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class BThread extends Thread {  

public BThread() {
super("[BThread] Thread");
};

public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
try {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + " loop at " + i);
Thread.sleep(1000);
}
System.out.println(threadName + " end.");
} catch (Exception e) {
System.out.println("Exception from " + threadName + ".run");
}
}
}

public class AThread extends Thread {

BThread bt;

public AThread(BThread bt) {
super("[AThread] Thread");
this.bt = bt;
}

public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
try {
bt.join();
System.out.println(threadName + " end.");
} catch (Exception e) {
System.out.println("Exception from " + threadName + ".run");
}
}
}

public class TestDemo {

public static void main(String[] args) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
BThread bt = new BThread();
AThread at = new AThread(bt);
try {
bt.start();
Thread.sleep(2000);
at.start();
at.join();
} catch (Exception e) {
System.out.println("Exception from main");
}
System.out.println(threadName + " end!");
}

}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
main start.                  -- 主线程启动, 因为主线程中调用了at.join(), 所以主线程要等到 AThread线程结束之后才能执行  
[BThread] Thread start.
[BThread] Thread loop at 0
[BThread] Thread loop at 1
[AThread] Thread start. -- AThread线程启动, 因为AThread线程中调用了bt.join(), 所以AThread线程要等到BThread线程结束之后才能执行
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end. -- BThread线程结束了, AThread线程执行
[AThread] Thread end. -- AThread线程结束了, 主线程执行
main end!
  1. 深入的了解join()的用法:

网上有很多人是这样解释 join()的用法的:”主线程等待子线程的终止“ ,相信有很多人都会这么说,但是这个说法是完全错误的,为什么呢?
请看例子,在上边代码的基础上,我们对TestDemo类做一下改动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestDemo {  

public static void main(String[] args) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
BThread bt = new BThread();
AThread at = new AThread(bt);
try {
bt.start();
Thread.sleep(2000);
at.start();
// at.join(); //这里注释掉
} catch (Exception e) {
System.out.println("Exception from main");
}
System.out.println(threadName + " end!");
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
main start.                     -- 主线程启动  
[BThread] Thread start. -- BThread线程启动
[BThread] Thread loop at 0
[BThread] Thread loop at 1
main end! -- 主线程结束,(也就是说AThread线程中调用了bt.join()并不会影响到主线程)
[AThread] Thread start. -- AThread线程启动, 因为AThread线程中调用了bt.join(), 所以AThread线程要等到BThread线程结束之后才能执行
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end. -- BThread线程结束了, AThread线程执行
[AThread] Thread end.

相信聪明的读者已经猜到为什么说 ”主线程等待子线程的终止 “ 的错误原因了吧,正确的说法应该是:”当前线程等待子线程的终止“

从源码看join()方法

在AThread的run方法里,执行了bt.join();,进入看一下它的JDK源码:

1
2
3
public final void join() throws InterruptedException {
join(0L);
}

然后进入join(0L)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
// 如果线程被生成了,但还未被起动,isAlive()将返回false,调用它的join()方法是没有作用的,将直接继续向下执行。
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

单纯从代码上看:在AThread类中的run方法中,bt.join()是判断bt的active状态,如果bt的isActive()方法返回false,在bt.join(),这一点就不用等待BThread线程结束,AThread就可以继续向下进行。

isAlive()方法的签名是:public final native boolean isAlive(),也就是说isAlive()是判断当前线程的状态。

TheadLocal

Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。

创建一个ThreadLocal对象

如下所示,创建一个ThreadLocal变量:

1
private ThreadLocal myThreadLocal = new ThreadLocal();

你实例化了一个ThreadLocal对象。每个线程仅需要实例化一次即可。虽然不同的线程执行同一段代码时,访问同一个ThreadLocal变量,但是每个线程只能看到私有的ThreadLocal实例。所以不同的线程在给ThreadLocal对象设置不同的值时,他们也不能看到彼此的修改。

访问ThreadLocal对象

一旦创建了一个ThreadLocal对象,你就可以通过以下方式来存储此对象的值:

1
myThreadLocal.set("A thread local value");

也可以直接读取一个ThreadLocal对象的值:

1
String threadLocalValue = (String) myThreadLocal.get();

get()方法会返回一个Object对象,而set()方法则依赖一个Object对象参数。

ThreadLocal泛型

为了使get()方法返回值不用做强制类型转换,通常可以创建一个泛型化的ThreadLocal对象。以下就是一个泛型化的ThreadLocal示例:

1
private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();

现在你可以存储一个字符串到ThreadLocal实例里,此外,当你从此ThreadLocal实例中获取值的时候,就不必要做强制类型转换。

1
2
3
myThreadLocal1.set("Hello ThreadLocal");

String threadLocalValues = myThreadLocal.get();

初始化ThreadLocal

由于ThreadLocal对象的set()方法设置的值只对当前线程可见,那有什么方法可以为ThreadLocal对象设置的值对所有线程都可见。

为此,我们可以通过ThreadLocal子类的实现,并覆写initialValue()方法,就可以为ThreadLocal对象指定一个初始化值。如下所示:

1
2
3
4
5
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override protected String initialValue() {
return "This is the initial value";
}
};

此时,在set()方法调用前,当调用get()方法的时候,所有线程都可以看到同一个初始化值。

Full ThreadLocal Example

以下是一个完整的ThreadLocal示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package cn.caoler.ThreadTest2;

/**
* Author: Caole
* CreateDateTime: 2017/11/27 13:21
* Description:
*/
public class ThreadLocalExample {

public static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal =
new ThreadLocal<Integer>();
@Override
public void run() {
threadLocal.set( (int) (Math.random() * 100D) );
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(threadLocal.get());
}
}

public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); //wait for thread 1 to terminate
thread2.join(); //wait for thread 2 to terminate
}
}

上面创建了两个线程共享一个MyRunnable实例。每个线程执行run()方法的时候,会给同一个ThreadLocal实例设置不同的值。如果调用set()方法的时候用synchronized关键字同步,而不是ThreadLocal对象的实例,那么第二个线程将会覆盖第一个线程所设置的值。

然而,由于是ThreadLocal对象,所以两个线程无法看到彼此的值。因此,可以设置或者获取不同的值。

InheritableThreadLocal

InheritableThreadLocal类是ThreadLocal的子类。为了解决ThreadLocal实例内部每个线程都只能看到自己的私有值,所以InheritableThreadLocal允许一个线程创建的所有子线程访问其父线程的值。

参考

0%