上次梳理了线程的创建和状态切换,本次将继续基础篇的整理,涉及JAVA的两类线程,以及ThreadLocal的一些知识、源码等。
1 ThreadLocal
1.1 概述
ThreadLocal是JDK包提供的,在java.lang包下,提供了线程本地变量。
它的作用是,当创建了一个ThreadLocal变量,每个访问这个变量的线程,都会有一个这个变量的本地副本,当多个线程操作这个变量时,实际操作的是自己本地内存的变量,互不干扰。
如此一来,它的使用场景主要针对多线程,变量不共享的情况:
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,Session会话管理。
1.2 怎么用
通过声明ThreadLocal变量,不同的线程单独维护一个备份。
public class ThreadLocalTest {
// 创建localVariable变量
private static ThreadLocal<String> localVariable = new ThreadLocal<>();
static void printAndRemove(String str){
// 打印当前线程本地内存中的localVariable值
System.out.println(str + ":" + localVariable.get());
// 移出当前线程本地内存中的localVariable值
localVariable.remove();
}
public static void main(String[] args) {
// 线程1
Thread threadOne = new Thread(() -> {
localVariable.set("threadOne local variable 11");
printAndRemove("threadOne");
System.out.println("threadOne local variable remove" + ":" + localVariable.get());
});
// 线程2
Thread threadTwo = new Thread(() -> {
localVariable.set("threadTwo local variable 22");
printAndRemove("threadTwo");
System.out.println("threadTwo local variable remove" + ":" + localVariable.get());
});
// 开启线程
threadOne.start();
threadTwo.start();
}
}
threadOne:threadOne local variable 11
threadOne local variable remove:null
threadTwo:threadTwo local variable 22
threadTwo local variable remove:null
1.3 源码实现
看一下相关的类图
ThreadLocalMap是一个定制化的HashMap,在Thread类中有两个ThreadLocalMap类型的变量:threadLocals
和inheritableThreadLocals
,在默认情况下,每个线程的这两个变量为null,只有当前线程第一次调用ThreadLocal的set或get方法,才会创建他们。
为什么会是一个map结构呢,从变量名可以看出,一个线程(threadLocals)可以关联多个threadLocal变量。
所以按上面的代码中,ThreadLocal变量实例里,并不存放具体某个线程的变量,只是一个工具壳,通过set方法将value值放到调用线程的threadLocals里,调用get时,再从当前线程中取出来。每个线程的本地变量存放在线程内存空间中,如果线程不终止,那么该本地变量将会一直存放在调用线程的threadLocals中,所以当不需要使用时,可以调用remove从当前线程中删除掉。
经过上面的分析,有了大致的概念,来看一下具体的源码实现
1.3.1 set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
如上是源码,比较简单:首先获取当前线程,然后以当前线程为KEY,查找对应的线程变量,找到变量则设置值,找不到则认为是第一次调用,创建当前线程对应的map。
其中getMap()
方法,就是拿到threadLocals变量,createMap
就是调用ThreadLocalMap的构造函数,实例化对象。源码如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
1.3.1 get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
总的体操作和set类似,首先拿到当前线程去取threadLocals,然后对这个map进行操作,值得注意的是,这个map的key是当前ThreadLocal实例(代码中的this),实现了一个线程对应多个ThreadLocal变量。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
定义了一个Entry来保存数据,继承的弱引用。在Entry内部使用ThreadLocal作为key,使用传递的值作为value。
1.3.3 remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
同样是获得当前线程之后,拿到线程对应的map,然后以当前ThreadLocal实例为key进行remove,删除当前ThreadLocal变量。
1.4 InheritableThreadLocal类
1.4.1 概述原理
通过上面的分析,知道了ThreadLocal变量,是每一个线程维护一个本地备份,从源码角度是对应了Thread的threadLocals
。这样一来,不论两个线程之间是否存在父子关系,变量对对方来说,都是不可见的。
InheritableThreadLocal继承自ThreadLocal,提供了一个特性,就是让子线程可以访问父线程设置的本地变量。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
可以看出,主要重写了三个方法:
-
重写了createMap之后,再次调用set的时候,将会创建当前线程的inheritableThreadLocals变量,而不是threadLocals变量。
-
重写了getMap之后,返回的也是inheritableThreadLocals。
可以看出来,在InheritableThreadLocal的中,使用inheritableThreadLocals代替了threadLocals变量。
-
对childValue的重写,要看看Thread的默认构造函数:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
// 获取当前线程
Thread parent = currentThread();
...
// 如果父线程的inheritableThreadLocals不为空
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 设置子线程中的inheritableThreadLocals变量
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
// ThreadLocal.createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
在创建线程的时候,构造函数调用init()方法,然后再init中,首先拿到了当前线程(这里指父线程),然后判断inheritableThreadLocals是否为null,不为空的话,将会执行createInheritedMap()方法,即使用父线程的inheritableThreadLocals变量作为构造函数参数,创建了一个新的ThreadLocalMap变量,然后复制给子线程的inheritableThreadLocals变量
整个流程就清楚了,即在创建子线程的时候(当前线程为父线程),就已经拿父线程的inheritableThreadLocals变量传参,复制到子线程的inheritableThreadLocals中。还剩一个方法源码没有看,就是ThreadLocalMap(parentMap),主要实现的就是将传入的值,复制出来。
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 使用到了重写的第三个方法childValue
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
1.4.2 使用
在实例化时,对象换成 InheritableThreadLocal。
场景应用于,子线程需要使用到父线程的本地变量。
private static ThreadLocal<String> local = new InheritableThreadLocal<>();
public static void main(String[] args) {
local.set("main");
System.out.println("main" + ":" + local.get());
Thread thread = new Thread(() -> {
System.out.println("thread" + ":" + local.get());
});
thread.start();
}
main:main
thread:main
2 守护线程和用户线程
2.1 概述
守护(daemom)线程,用户(user)线程,比较典型的例子是:守护线程:垃圾回收线程;用户线程:main主线程。
主要的区别就是,当非守护线程结束时,其余的线程(守护线程)不再等待,JVM退出。 即非守护线程不影响JVM的退出。
这样一来,可以利用这个规律,设置一些线程为守护线程,就不需要手动管理它的退出。
2.2 区别
创建守护线程:在开始线程前,通过设置标记位的方式即可。
Thread daemonThread = new Thread(new Runnable(){
public void run(){
// do something
}
});
// 设置为守护线程
daemonThread.setDaemon(true);
daemonThread.start();
一个直观的例子,感受守护线程与用户线程的区别:
①首先看创建一个用户线程的情况:
public class DaemonThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true){
}
});
thread.start();
System.out.println("main thread over");
}
}
开启了一个用户线程类型的子线程是一个死循环,主线程执行完成之后,子线程还在进行,程序并没有执行结束。
②再来看看,创建的子线程是守护线程的情况下:
public class DaemonThreadTest {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while(true){
}
});
// 设置为守护线程
daemonThread.setDaemon(true);
daemonThread.start();
System.out.println("main thread over");
}
}
虽然子线程依旧是死循环,但是类型为守护线程时,主线程执行完成之后,用户线程全部执行完毕,JVM关闭,程序结束。
2.3 实现
当main
线程结束之后,JVM会自动启动一个叫作DestroyjavaVm
的线程,该线程会等待所有用户线程结束后,终止JVM进程。
这里牵扯出的JVM代码如下:
int JNICALL
JavaMain(void *_args){
...
// 执行Java中的main函数
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
// main函数返回值
ret = (*env)->ExceptionOccurred(env) == NULL ? 0: 1;
// 等待所有非守护线程结束,然后销毁JVM进程。
LEAVE();
}
LEAVE()是C语言的宏定义,创建了一个名为DestroyjavaVm
的线程,来等待所有用户线程结束。
#define LEAVE()
do{
if ((*vm)->DetachCurrentThread(vm) != JNI_OK) {
JLI_ReportErrorMessage(JVM_ERROR2);
ret = 1;
}
if (JNI_TRUE){
(*vm)->DestroyJavaVM(vm);
return ret;
}
} while (JNI_FALSE)
相对的,在Tomcat的NIO实现NioEndpoint中会开启一组线程来接受用户的连接请求,以及一组处理线程负责处理具体的用户请求,查看源码,可以看到对daemon属性置为true的操作,故这两组接受和处理线程都属于守护线程。
简单的说,当希望主线程执行结束之后,JVM进行就马上结束,那么可以将创建的其他线程设置为守护线程;当希望主线程执行完成之后,子线程继续执行,那么将子线程设置为守护线程。
参考文献:《java并发编程之美》