死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去

线程A 己经持有了资源2 , 它同时还想申请资源l , 线程B 已经持有了资源l ,它同时还想申请资源2 , 所以线程l 和线程2 就因为相互等待对方已经持有的资源,而进入了死锁状态。
为什么会产生死锁呢?用原书(并发编程之美)中的解释
死锁的产生必须具备以下四个条件。互斥条件: 指线程对己经获取到的资源进行排它性使用, 即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。请求并持有条件: 指一个线程己经持有了至少一个资源, 但又提出了新的资源请求,而新资源己被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己己经获取的资源。不可剥夺条件: 指线程获取到的资源在自己使用完之前不能被其他线程抢占, 只有在自己使用完毕后才由自己释放该资源。环路等待条件: 指在发生死锁时, 必然存在一个线程→资源的环形链, 即线程集合{T0 , T1 T2 ,…, Tn }中的T0 正在等待一个Tl 占用的资源, T1正在等待T2 占用的资源,……Tn 正在等待己被T0 占用的资源。
死锁例子
public class DedLock {
//创建资源
private static Object resourceA = new Object();
private static Object resourceB = new Object();
public static void main(String[] args) {
new Thread(()->{
//先给我们的资源A加锁
synchronized (resourceA){
System.out.println(Thread.currentThread()+"获得资源A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"等待获取资源B");
//在对资源B加锁
synchronized (resourceB)
{
System.out.println(Thread.currentThread()+"获得资源B");
}
}
}).start();
new Thread(()->{
//先给我们的资源A加锁
synchronized (resourceB){
System.out.println(Thread.currentThread()+"获得资源B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"等待获取资源A");
//在对资源B加锁
synchronized (resourceA)
{
System.out.println(Thread.currentThread()+"获得资源B");
}
}
}).start();
}
}
结果
Thread[Thread-0,5,main]获得资源A
Thread[Thread-1,5,main]获得资源B
Thread[Thread-1,5,main]等待获取资源A
Thread[Thread-0,5,main]等待获取资源B
这就是典型的死锁例子,我们先锁了资源A,在资源A中又对资源B加锁,但是因为我们线程A睡了1s,期间我们的线程B已经对资源B先加锁了,线程B先对资源B加锁但是又想获取资源A,就导致两者互相争抢,又不释放自己的锁,导致死锁
他们满足上面我们说的死锁产生具备的条件么?
1.resourc eA 和re sourc eB 都是互斥资源,当线程A 调synchronized(resource A)方法获取到resourceA 上的监视器锁并释放前, 线程B 再调用synchronized(resourceA) 方法尝试获取该资源会被阻塞,只有线程A 主动释放该锁, 线程B 才能获得, 这满足了资源互斥条件
2.线程A 首先通过synchronized(resourceA) 方法获取到resourceA 上的监视器锁资源,然后通过synchronized(resourceB) 方法等待获取resourceB 上的监视器锁资源, 这就构成了请求并持有条件。
也就是请求B但是我还持有A
3.构成了资源的不可剥夺条件,就是我们线程A只要不是自己主动释放资源A的监视器锁,那么其他线程(线程B)是不会掠夺走的
4.环路等待条件就是,线程A锁了资源A但是请求资源B,但是线程B锁了资源B,又去请求资源A,形成闭合
那么如何避免线程死锁呢?
字需要破坏形成死锁的一个必要条件即可。
目前我们只有请求并持有和环路等待条件是可以被破坏的
造成死锁的原因其实和申请资源的顺序有很大关系, 使用资源申请的有序性原则就可以避免死锁,那么什么是资源申请的有序性呢?我们对上面线程B 的代码进行如下修改
new Thread(()->{
//先给我们的资源A加锁
synchronized (resourceA){
System.out.println(Thread.currentThread()+"获得资源B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"等待获取资源A");
//在对资源B加锁
synchronized (resourceB)
{
System.out.println(Thread.currentThread()+"获得资源A");
}
}
}).start();
结果
Thread[Thread-0,5,main]获得资源A
Thread[Thread-0,5,main]等待获取资源B
Thread[Thread-0,5,main]获得资源B
Thread[Thread-1,5,main]获得资源B
Thread[Thread-1,5,main]等待获取资源A
Thread[Thread-1,5,main]获得资源A
也就是说资源B没有被加锁,虽然两个线程都对资源A加锁了,但是某个线程先执行,好后获得B之后就是放了资源A,另一个线程能拿到了
这就是资源的有序分配,资源的有序性破坏了资源的请求并持有条件和环路等待条件, 因此避免了死锁。
Java 中的线程分为两类,分别为daemon 线程(守护线程〉和user 线程(用户线程)。
在JVM启动的时候会调用main函数,main函数所在的线程就是一个用户线程
当然JVM内部同时还启动了好多守护线程,比如垃圾回收线程。
关于垃圾回收线程后续会在JVM的笔记中详细提到
如何创建守护线程
public class daemon {
public static void main(String[] args) {
Thread thread = new Thread(()->{});
//设置为守护线程
thread.setDaemon(true);
thread.start();
}
}
用户线程与守护线程的区别
区别之一是当最后一个非守护线程结束时, NM 会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响NM 的退出。言外之意,只要有一个用户线程还没结束, 正常情况下NM 就不会退出。
public class daemon {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (;;){}
});
//启动子线程
thread.start();
System.out.println("主线程完毕");
}
}
结果是
主线程完毕
如上代码在main 线程中创建了一个thread 线程,在thread 线程里面是一个无限循环。从运行代码的结果看, main 线程已经运行结束了,那么JVM进程己经退出了吗?在IDE的输出结果右上侧的红色方块说明,JVM 进程并没有退出。另外,我们亦可以通过jps来查看
这个结果说明了当父线程结束后,子线程还是可以继续存在的,也就是子线程的生命周期并不受父线程的影响。这也说明了在用户线程还存在的情况下JVM 进程并不会终止。那么我们把上面的thread 线程设置为守护线程后,再来运行看看会有什么结果:
public class daemon {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (;;){}
});
//设置为守护线程
thread.setDaemon(true);
//启动子线程
thread.start();
System.out.println("主线程完毕");
}
}
结果是
主线程完毕
进程已结束,退出代码 0
在启动线程前将线程设置为守护线程,执行后的输出结果显示,JVM进程己经终止了,执行***ps -eaf |grep java*** 也看不到JVM 进程了。在这个例子中, main 函数是唯一的用户线程, thread 线程是守护线程,当main 线程运行结束后, JVM 发现当前己经没有用户线程了,就会终止JVM 进程。
由于这里的守护线程执行的任务是一个死循环,这也说明了如果当前进程中不存在用户线程,但是还存在正在执行任务的守护线程,则JVM不等守护线程运行完毕就会结束JVM进程。
main 线程运行结束后, JVM会自动启动一个叫作DestroyJava VM 的线程, 该线程会等待所有用户线程结束后终止JVM进程。下面通过简单的JVM代码来证明这个结论
int JNICALL
JavaMain(void * args)
//执行Java 中的ma 工n函数
(*env) - >CallStaticVoidMethod(env , mainClass, mainID, mainArgs) ;
//main 函数返回值
ret = (*env)->ExceptIonOccurred(env) == NULL ? 0: 1 ;
//等待所有非守护线程结束, 然后销毁♂月4进程
LEAVE();
}
LEAVE 是C 语言里面的一个宏定义,具体定义如下。
#define LEAVE () \
do {\
if ( (*vm) >DetachCurrentThread(vm) 1= JNI_OK ) { \
JLI_ReportErrorMessage(JVM_ERROR2) ; \
ret = l ; \
}\
if (JNI_TRUE) { \
( *vm)->DestroyJavaVM (vm); \
return ret; \
}\
} while_(JNI FALSE)
该宏的作用是创建一个名为DestroyJava VM 的线程,来等待所有用户线程结束
在Tomcat 的NIO 实现NioEndpoint 中会开启一组接受线程来接受用户的连接请求,以及一组处理线程负责具体处理用户请求,那么这些线程是用户线程还是守护线程呢?
下面我们看一下NioEndpoint 的startlntemal 方法。
public void startInternal() throws Exception{
if(!running){
running = true;
paused = false;
...
//创建处理线程
pollers = new Poller[get PollerThreadCount () ] ;
for (int i=O ; i<pollers . length; i++) {
pollers [i] = new Poller () ;
Thread pollerThread =new Thread (pollers [i],getName () +"- Client Poller-"+ i ) ;
pollerThread.setPriority (threadPriority ) ;
pollerThread.setDaemon(true );
//声明为守护线程
pollerThread.start() ;
}
}
protected final void startAcceptorThreads() {
int count= getAcceptorThre adCount();
acceptors= new Acceptor[count];
for (int i = O;i<count; i ++) {
acceptors [i] = createAcceptor () ;
String threadName = getName ()+"- Acceptor-" +i;
acceptors[i].setThreadName(threadName) ;
Thread t =new Thread(acceptors[i],threadName) ;
t.setPriority(getAcceptorThreadPriority()) ;
t.setDaemon(getDaemon());//设置是否为守护线程,默认为守护线程
t . start() ;
}
}
private boolean daemon = true;
public void setDaemon(boolean b) { daemon= b ; }
public boolean getDaemon () { return daemon ; }
在如上代码中,在默认情况下, 接受线程和处理线程都是守护线程, 这意味着当tomcat 收到shutdown 命令后并且没有其他用户线程存在的情况下tomcat 进程会马上消亡,而不会等待处理线程处理完当前的请求。
总结
如果你希望在主线程结束后JVM 进程马上结束,那么在创建线程时可以将其设置为守护线程,如果你希望在主线程结束后子线程继续工作,等子线程结束后再让JVM进程结束,那么就将子线程设置为用户线程。
多钱程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步,如下图所示

我们的同步方式一般的都是用加锁的方式来实现同步,那么有没有一种方式可以做到,当创建一个变量后, 每个线程对其进行访问的时候访问的是自己线程的变量呢?====>ThreadLocal
什么是ThreadLocal
ThreadLocal 是JDK 包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal 变量后,每个线程都会复制一个变量到自己的本地内存,如下图所示。

public class ThreadLocalTest {
static void print(String str){
//1.1打印当前线程本地内存中localVariable变量的值
System.out.println(str+":"+localVairable.get());
//1.2清除当前线程池本地内存中的localVairable变量
//localVairable.remove();
}
//2.创建ThreadLocal变量
static ThreadLocal<String> localVairable = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
//3.1设置线程A中本地变量localVairable的值
localVairable.set("线程A的localVairable");
//3.2调用打印函数
print("线程A");
//3.3打印本地变量的值
System.out.println("线程A的localVairable清除后:"+localVairable.get());
}).start();
new Thread(()->{
//3.1设置线程A中本地变量localVairable的值
localVairable.set("线程B的localVairable");
//3.2调用打印函数
print("线程B");
//3.3打印本地变量的值
System.out.println("线程B的localVairable清除后:"+localVairable.get());
}).start();
}
}
结果是:
线程A:线程A的localVairable
线程B:线程B的localVairable
线程A的localVairable清除后:线程A的localVairable
线程B的localVairable清除后:线程B的localVairable
我们使用了set设置了localVariable 的值,这其实是设置A线程本地内存的一个副本,这个副本B是访问不了的。
上面的例子是我们没有执行清除本地内存副本的操作,我们放开注释执行,结果变为
线程A:线程A的localVairable
线程B:线程B的localVairable
线程A的localVairable清除后:null
线程B的localVairable清除后:null
相关类图

书中说道
Thread 类中有一个threadLocals 和一个inheritableThreadLocals , 它们都是ThreadLocalMap 类型的变量, 而ThreadLocalMap 是一个定制化的Hashmap 。在默认情况下, 每个线程中的这两个变量都为null ,源码中就有体现
protected T initialValue() {
return null;
}
只有当前线程第一次调用ThreadLocal 的set 或者get 方法时才会创建它们。
其实每个线程的本地变量不是存放在ThreadLocal 实例里面,而是存放在调用线程的threadLocals 变量里面。也就是说, **ThreadLocal 类型的本地变量存放在具体的线程内存空间中。**ThreadLocal 就是一个工具壳,
它通过set 方法把value 值放入调用线程的threadLocals 里面并存放起来, 当调用线程调用它的get 方法时,再从当前线程的threadLocals 变量里面将其拿出来使用。如果调用线程一直不终止, 那么这个本地变量会一直存放在调用线程的threadLocals 变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal 变量的remove 方法,从当前线程的threadLocals 里面删除该本地变量。
另外, Thread 里面的threadLocals 为何被设计为map 结构?很明显是因为每个线程可以关联多个ThreadLocal 变量。
1 void set(T value)
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//将当前线程作为key,去查找对应的线程变量
ThreadLocalMap map = getMap(t);
//如果存在这个线程对应的变量
if (map != null)
map.set(this, value);
else
//如果对应的线程变量不存在,就新建,将key,value保存
createMap(t, value);
}
getMap(Thread t)的源码如下。
ThreadLocalMap getMap(Thread t) {
//传进来一个线程,返回这个线程的变量threadLocals
return t.threadLocals;
}
可以看到, getMap(t)的作用是获取线程自己的变量threadLocal s, threadlocal 变量被绑定到了线程的成员变量上
如果getMap(t)的返回值不为空,则把value 值设置到threadLocals 中,也就是把当前变量值放入当前线程的内存变量threadLocals 中
threadLocals 是一个HashMap 结构, 其中key 就是当前ThreadLocal 的实例对象引用, value 是通过set 方法传递的值。
如果getMap(t)返回空值则说明是第一次调用set 方法,这时创建当前线程的threadLocals 变量。下面来看**createMap(t, value)**做什么。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
也就是说当前线程没有threadLocals这个变量时,她就new一个
ThreadLocalMap线程作为键,设置的内容为值
T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//查找当前线程存不存在对应的threadLocals变量
ThreadLocalMap map = getMap(t);
//如果threadLocals变量不为null,则返回对应本地变量的值
if (map != null) {
//
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//取出我们的值 @SuppressWarnings正压警告注解
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果没有threadLocals这个变量,则初始化一个
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private T setInitialValue() {
//初始化为null
T value = initialValue();
//获得当前线程
Thread t = Thread.currentThread();
//查找当前线程存不存在对应的threadLocals变量
ThreadLocalMap map = getMap(t);
if (map != null)
//有的话值设置为null
map.set(this, value);
else
//没有的话创建,
createMap(t, value);
return value;
}
如果当前线程的threadLocal s 变量不为空, 则设置当前线程的本地变量值为null , 否则调用createMap 方法创建当前线程的createMap 变量。
3.void remove()
public void remove() {
//查询该线程有没有绑定的threadLocals变量
ThreadLocalMap m = getMap(Thread.currentThread());
//如果有,移除
if (m != null)
m.remove(this);
}
总结
在每个线程内部都有一个名为threadLocals 的成员变量, 该变量的类型为Hash Map , 其中key 为我们定义的ThreadLocal 变量的this 引用, value 则为我们使用set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals 中,如果当前线程一直不消亡, 那么这些本地变量会一直存在, 所以可能会造成内存溢出, 因此使用完毕后要记得调用ThreadLocal 的remove 方法删除对应线程的threadLocals 中的本地变量。在高级篇要讲解的只JC 包里面的ThreadLocalRandom , 就是借鉴ThreadLocal 的思想实现的, 后面会具体讲解。
首先看一个例子
public class TestThreadLocal {
//创建线程变量
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
//2.设置线程变量
threadLocal.set("你好,world");
//3.启动子线程
new Thread(()->{
System.out.println("子线程:"+threadLocal.get());
}).start();
//4.输出主线成的变量值
System.out.println("main:"+threadLocal.get());
}
}
结果是:
main:你好,world
子线程:null
也就是说,同一个ThreadLocal 变量在父线程中被设置值后, 在子线程中是获取不到的。根据上节的介绍,这应该是正常现象,因为在子线程thread 里面调用get 方法时当前线程为thread 线程,而这里调用s et 方法设置线程变量的是main 线程,两者是不同的线程,自然子线程访问时返回null 。
那如果我们把set放到子线程的函数体里面,结果就会是
main:null
子线程:你好,world
那么有没有办法让子线程能访问到父线程中的值? 答案是有。
InheritableThreadLocal继承自ThreadLocal , 其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。
下面看一下InheritableThreadLocal 的代码。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
//1
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);
}
}
Inheritab I e ThreadLocal 继承了ThreadLocal ,并重写了三个方法
InheritableThreadLocal 重写了createMap 方法, 那么现在当第一次调用set 方法时,创建的是当前线程的inheritableThreadLocals 变量的实例而不再是threadLocals 。
当调用get 方法获取当前线程内部的map 变量时, 获取的是inheritableThreadLocals 而不再是threadLocals 。
也就是说我们用了lnheritableThreadLocal 类的话,threadLocals 变成了inheritableThreadLocals
childValue
下面我们看一下重写的代码( 1 )何时执行, 以及如何让子线程可以访问父线程的本地变量。这要从创建Thread 的代码说起,打开Thread 类的默认构造函数,代码如下。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init (ThreadGroup g , Runnable target , String name,long stacksize , AccessControlContext ace) {
//(4 )获取当前线程 这里获取的是我们的main线程
Thread parent = currentThread( );
//(5 )如采父线程的inheritableThreadLocals变量不为null
if (parent.inheritableThreadLocals != null )
//(6 )设置子线程中的inheritableThreadLocals变量
this.inheritableThreadLocals =
ThreadLocal.createinheritedMap(parent.inheritableThreadLocals);
this .stackSize = stackSize;
tid = nextThreadID() ;
}
代码在创建线程时,在构造函数里面会调用in it 方法。代码( 4 )获取了当前线程(这里是指main 函数所在的线程,也就是父线程〉。
然后代码( 5 )判断main 函数所在线程里面的inheritableThreadLocals 属性是否为null 。
前面我们讲了InheritableThreadLocal 类的get 和set 方法操作的是inheritableThreadLocals ,所以这里的inheritableThreadLocal 变量不为null ,因此会执行代码( 6 )。下面看一下createlnh eritedMap 的代码
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
可以看到,在createlnheri tedMap 内部使用父线程的inheritableThreadLocals 变量作为构造函数创建了一个新的ThreadLocalMap 变量, 然后赋值给了子线程的inheritableThreadLocals 变量
下面我们看看在ThreadLocalMap 的构造函数内部都做了什么事情
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) {
//调用重写的方法
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++;
}
}
}
}
在该构造函数内部把父线程的inheritabl eThreadLoca l s 成员变量的值复制到新的ThreadLoca!Map 对象中,其中l代码( 7 )调用了Inheri tab leThreadLocal 类重写的代码( 1 ) 。
总结
InheritableThreadLocal 类通过重写代码。〉和( 3 ) 让本地变量保存到了具体线程的inheritableThreadLocal s 变量里面,那么线程在通过InheritableThreadLocal 类实例的set 或者get 方法设置变量时,就会创建当前线程的inheritableThreadLocals 变量。当父线程创建子线程时,构造函数会把父线程inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals 变量里面。
把1.11.3 节中的代码( 1 )修改为
public class TestThreadLocal {
//创建线程变量
public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
//2.设置线程变量
threadLocal.set("你好,world");
//3.启动子线程
new Thread(()->{
System.out.println("子线程:"+threadLocal.get());
}).start();
//4.输出主线成的变量值
System.out.println("main:"+threadLocal.get());
}
}
结果为
main:你好,world
子线程:你好,world
那么在什么情况下需要子线程可以获取父线程的threadlocal 变量呢?
情况还是蛮多的,比如子线程需要使用存放在threadlocal 变量中的用户登录信息,再比如一些中间件需要把统一的id 追踪的整个调用链路记录下来。
其实子线程使用父线程中的threadlocal 方法有多种方式, 比如创建线程时传入父线程中的变量,并将其复制到子线程中,或者在父线程中构造一个map 作为参数传递给子线程,但是这些都改变了我们的使用习惯,所以在这些情况下InheritabI e ThreadLocal 就显得比较有用。
