Java并发编程之线程

线程作为操作系统的最小调度单位。一个进程里可以有多个线程,这些线程有各自的程序计数器、堆栈空间和局部变量,而且可以共享进程的内存空间,因而在上下文切换时时间更短,效率更高,也常被成为轻量级进程(Light Weight Process)。接下来,将从三个方面来介绍Java线程类Thread的具体用法:

  • 线程构造与初始化;
  • 线程状态切换;
  • 线程变量ThreadLocal的使用

Thread: https://en.wikipedia.org/wiki/Thread_(computing)

线程的构造与初始化

Thread类提供了多个构造函数,初始化时有四个主要的参数:

  • ThreadGroup 线程所在的线程组;
  • Runnable目标可执行对象;
  • String 线程名称;
  • long stackSize,线程栈空间大小,不指定(为0)时由JVM确定;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}

....

public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}

线程的构造函数最后通过一个init函数来进行初始化,初始化主要完成完成几个事情:

  • 如果用户没有指定ThreadGroup,则首先需要确定该线程的ThreadGroup;
  • 设置守护进程属性以及线程优先级;
  • 创建ThreadLocal变量对应的ThreadLocalMap对象;
  • 产生线程ID
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
 
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
// 确定ThreadGroup
if (g == null) {
/* Determine if it's an applet or not */

/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}

/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
// 增加该线程组中未启动线程的数目
g.addUnstarted();

this.group = g;
// 是否为守护线程/优先级都由父亲线程确定
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();

this.target = target;
setPriority(priority);
// 创建ThreadLocal变量对应的ThreadLocalMap
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;

/* 设置线程ID */
tid = nextThreadID();
// 初始化完成
this.me = this;
}

线程构造完成后,调用start()即可启动了。

线程状态

Thread.java源码,线程在JVM中有6种不同的状态:

  • NEW: 线程刚创建,还未启动;
  • RUNNABLE: 线程在JVM中开始执行
  • BLOCKED: 线程在等待某个锁时被阻塞
  • WAITING: 线程处于无限期等待另一个线程完成某个动作
  • TIMED_WAITING: 线程等待另一个线程完成某个动作,超时后返回执行;
  • TERMINATED: 线程处于终止状体(执行完成或者推出)

下图是一个线程状态之间的切换图:

线程创建完成后,调用start开始执行。当线程调用wait方法之后,开始进入等待状态,这是需要在其他线程调用notify/notifyAll进行通知才能返回的运行状态,如果使用了超时等待,线程在等待指定时间长度之后,仍未受到通知,则直接返回运行。若线程尝试通过synchronized获取同步锁,则会进入阻塞状态;在执行完run方法中的代码之后,则会进入终止状态。

线程变量ThreadLocal的使用

线程本地变量ThreadLocal允许每个线程都有关于某个变量自己的一份唯一拷贝,线程可通过get/set方法来获取或者设置本地变量的值。通常,一个本地变量是一个static型、跟线程状态相关的变量(如用户ID ,交易ID)。

先来看下如何使用ThreadLocal本地变量。例如,我们想要得到一个线程的执行时间,可以这样利用ThreadLocal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class ThreadProfiler {
private static final ThreadLocal<Long> TIME = new ThreadLocal<Long>(){
protected Long initialValue(){
return System.currentTimeMillis();
}
};


public static final void start(){
TIME.set(System.currentTimeMillis());

}

public static final void end(){
TIME.set(System.currentTimeMillis());
}

public static final long getTime(){
return System.currentTimeMillis() - TIME.get();
}
}

这样只要在线程开始执行时调用ThreadProfiler.start(),而线程结束时调用ThreadProfiler.getTime()就可以得到线程执行所耗费的时间了。

ThreadLocal通过一个以ThreadLocal为key,其他任何对象为value的ThreadLocalMap(一个HashMap)来保存线程相关的本地变量值,而每个线程都有自己的一个ThreadLocalMap,这样线程就可以通过ThreadLocal来获取该map对象以ThreadLocal为键值的对象值了。

线程调用ThreadLocalset(T value)时,如果已存在一个map对象,则直接将该值以该ThreadLocal对象为键值保存下来;如果线程没有map对象,则需要创建一个新的ThreadLocalMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

线程调用ThreadLocal.get(),返回线程的本地变量。如果当前线程已经有一个map对象,并且该map对象有ThreadLocal对应的值,则直接返回;否则对线程的ThreadLocalMap对象threadLocals进行初始化操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}


private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

参考源码