一、死锁产生的四个必要条件
1.1、互斥
即当资源被一个线程使用(占有)时,别的线程不能使用
1.2、不可剥夺
资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
1.3、请求和保持
即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
1.4、循环等待
即存在一个等待闭环链路:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。
二、模拟死锁产生
假如有这样的一个需求,线程需要获取M
和N
两个数据资源之后,才能进行下一步的操作,M
和N
获取顺序没有先后之分
2.1、思路
定义两个数据资源M、N
,两个线程Thread-A、Thread-B
分别获取资源M
和资源N
两个资源
2.2、synchronized实现
package com.duchong.concurrent.thread;
import java.util.concurrent.TimeUnit;
/**
* 线程A和线程B同时去获取数据dataM和dataN
* 每个线程获取两个锁
* @author DUCHONG
* @since 2020-09-04 15:02
**/
public class DeadLockDemo {
//定义两个资源dataA和dataB
public static final String DATA_M="dataM";
public static final String DATA_N="dataN";
public static void main(String[] args) {
new Thread(new DeadThreadA(),"Thread-A").start();
new Thread(new DeadThreadB(),"Thread-B").start();
}
static class DeadThreadA implements Runnable{
public DeadThreadA() {
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---启动");
synchronized (DATA_M){
System.out.println(Thread.currentThread().getName()+"---获得数据M");
System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");
//睡一会,模拟 线程B锁住数据B
TimeUnit.SECONDS.sleep(3L);
synchronized (DATA_N){
System.out.println(Thread.currentThread().getName()+"---获取到数据N");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class DeadThreadB implements Runnable{
public DeadThreadB() {
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---启动");
synchronized (DATA_N){
System.out.println(Thread.currentThread().getName()+"---获得数据N");
System.out.println(Thread.currentThread().getName()+"---尝试获取数据M");
synchronized (DATA_M){
System.out.println(Thread.currentThread().getName()+"---获取到数据M");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.3、ReentrantLock实现
package com.duchong.concurrent.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程A和线程B同时去获取数据dataM和dataN
* 每个线程获取两个锁
* @author DUCHONG
* @since 2020-09-04 15:02
**/
public class DeadLockDemo3 {
static Lock lockM=new ReentrantLock();
static Lock lockN=new ReentrantLock();
public static void main(String[] args) {
new Thread(new DeadThreadA(),"Thread-A").start();
new Thread(new DeadThreadB(),"Thread-B").start();
}
static class DeadThreadA implements Runnable{
public DeadThreadA() {
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---启动");
lockM.lock();
System.out.println(Thread.currentThread().getName()+"---获得数据M");
System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");
//睡一会,模拟 线程B锁住数据B
TimeUnit.SECONDS.sleep(3L);
lockN.lock();
System.out.println(Thread.currentThread().getName()+"---获取到数据N");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class DeadThreadB implements Runnable{
public DeadThreadB() {
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---启动");
lockN.lock();
System.out.println(Thread.currentThread().getName()+"---获得数据N");
System.out.println(Thread.currentThread().getName()+"---尝试获取数据M");
lockM.lock();
System.out.println(Thread.currentThread().getName()+"---获取到数据M");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.4、运行结果
2.5、VisualVM查看
三、死锁解决方法
3.1、synchronized死锁解决方法
synchronized
实现的死锁是由于在一个线程里面获取了两个锁导致的,如果一个线程每次只能获取一个锁,那么就不会出现由于嵌套持有锁导致的死锁,要达到既能一个线程每次获取一个锁,有能获取到两个资源的效果,可以把资源数据抽取出来放在一个独立的Data
类里,然后让线程A
和线程B
去获取
3.1.1、Data类
package com.duchong.concurrent.thread;
/**
* 资源数据类-单独抽出来
* @author DUCHONG
* @since 2020-09-04 17:57:43
*/
public class Data{
public static final String DATA_M="dataM";
public static final String DATA_N="dataN";
/**
* 假如有一个线程持有dataM的锁,另一个线程进来,阻塞,更不用说进来的线程获取dataN了
* 先获取数据B
*/
public void getData(){
try {
System.out.println(Thread.currentThread().getName()+"---启动");
synchronized (Data.DATA_M){
System.out.println(Thread.currentThread().getName()+"---获得数据M");
System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");
synchronized (Data.DATA_N){
System.out.println(Thread.currentThread().getName()+"---获取到数据N");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.1.2、线程A和线程B
package com.duchong.concurrent.thread;
/**
* 将资源数据单独抽离出来
* 每隔线程获取一把锁
* @author DUCHONG
* @since 2020-09-04 15:02
**/
public class DeadLockDemo2 {
public static void main(String[] args) {
new Thread(new DeadThreadA(),"Thread-A").start();
new Thread(new DeadThreadB(),"Thread-B").start();
}
static class DeadThreadA implements Runnable{
public DeadThreadA() {
}
@Override
public void run() {
new Data().getData();
}
}
static class DeadThreadB implements Runnable{
public DeadThreadB() {
}
@Override
public void run() {
new Data().getData();
}
}
}
3.1.3、运行结果
没有发生死锁,符合预期
3.2、ReentrantLock死锁解决方法
利用tryLock()
方法尝试获取锁,设置超时时间为5s
,如果获取不成功,释放已占有的资源给另外一个线程
package com.duchong.concurrent.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程A和线程B同时去获取数据dataM和dataN
* 每个线程获取两个锁
* @author DUCHONG
* @since 2020-09-04 15:02
**/
public class DeadLockDemo4 {
static Lock lockM=new ReentrantLock();
static Lock lockN=new ReentrantLock();
public static void main(String[] args) {
new Thread(new DeadThreadA(),"Thread-A").start();
new Thread(new DeadThreadB(),"Thread-B").start();
}
static class DeadThreadA implements Runnable{
public DeadThreadA() {
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---启动");
//lockM 属于thread-a 独占
lockM.lock();
System.out.println(Thread.currentThread().getName()+"---获得数据M");
System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");
//睡一会,模拟 线程B锁住数据B
TimeUnit.SECONDS.sleep(3L);
//以下代码不执行,此时lockN 属于thread-b 独占,thread-b释放lockN之后,thread-a 获取到lockN,在执行下面的代码
lockN.lock();
System.out.println(Thread.currentThread().getName()+"---获取到数据N");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
//thread-a 获取到lockN之后,释放lockM
lockM.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class DeadThreadB implements Runnable{
public DeadThreadB() {
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"---启动");
//此时lockN 属于thread-b 独占
lockN.lock();
System.out.println(Thread.currentThread().getName()+"---获得数据N");
System.out.println(Thread.currentThread().getName()+"---尝试获取数据M");
//thread-b尝试获取thread-a的lockM 5s ,失败则释放thread-b独占的 lockN,以防死锁
if (lockM.tryLock(5L, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName()+"---获取到数据M");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
}
else{
//释放thread-b独占的 lockN,此时thread-a 可以获得lockN
lockN.unlock();
//thread-a 释放lockM后执行
lockM.lock();
System.out.println(Thread.currentThread().getName()+"---获取到数据M");
System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.2.1、运行结果
没有发生死锁。