一个常见的情景:多个线程互相等待对方解锁。
一、代码模拟:
线程1获得第一个锁,过了一会,准备获得第二个锁。
在线程1获得两个锁之间,线程2先获得了第二个锁,又准备获得第一个锁。
这时,形成循环等待,形成死锁。
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class ThreadByLock implements Runnable
{
public String name;
public Object lock1;
public Object lock2;
public String lock1Name;
public String lock2Name;
ThreadByLock(String name,Object lock1,Object lock2,String lock1Name,String lock2Name) {
this.name = name;
this.lock1 = lock1;
this.lock2 = lock2;
this.lock1Name = lock1Name;
this.lock2Name = lock2Name;
}
@Override
public void run()
{
synchronized (lock1){
System.out.println(
MessageFormat.format("{0},拿到{1}了",name,lock1Name)
);
System.out.println(
MessageFormat.format("{0},开始休眠",name)
);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
MessageFormat.format("{0},休眠结束",name)
);
synchronized (lock2){
System.out.println(
MessageFormat.format("{0},拿到{1}了",name,lock2Name)
);
}
System.out.println(
MessageFormat.format("{0},释放{1}了",name,lock2Name)
);
}
System.out.println(
MessageFormat.format("{0},释放{1}了",name,lock1Name)
);
}
}
public class deadlock {
private static final ReentrantLock lock1 = new ReentrantLock();
private static final ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) {
new Thread(new ThreadByLock("线程大A", lock1, lock2,"第一个锁","第二个锁")).start();
new Thread(new ThreadByLock("线程B",lock2,lock1,"第二个锁","第一个锁")).start();
}
}
二、解决办法:
(1)造成死锁的第四个因素是循环等待,通过规定加锁顺序可以避免循环等待。
具体实现为:先对标识符(比如哈希值)大的锁加锁。
tips:只在run方法最前面加了代码
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class ThreadByLock implements Runnable
{
public String name;
public Object lock1;
public Object lock2;
public String lock1Name;
public String lock2Name;
ThreadByLock(String name,Object lock1,Object lock2,String lock1Name,String lock2Name) {
this.name = name;
this.lock1 = lock1;
this.lock2 = lock2;
this.lock1Name = lock1Name;
this.lock2Name = lock2Name;
}
@Override
public void run()
{
if ( lock1.hashCode() > lock2.hashCode() ) {
// 简单的交换锁来改变顺序,临时使用
var temp = lock1; lock1=lock2; lock2 = temp;
var temp1 = lock1Name; lock1Name=lock2Name; lock2Name = temp1;
}
synchronized (lock1) {
System.out.println(
MessageFormat.format("{0},拿到{1}了", name, lock1Name)
);
System.out.println(
MessageFormat.format("{0},开始休眠", name)
);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
MessageFormat.format("{0},休眠结束", name)
);
synchronized (lock2) {
System.out.println(
MessageFormat.format("{0},拿到{1}了", name, lock2Name)
);
}
System.out.println(
MessageFormat.format("{0},释放{1}了", name, lock2Name)
);
}
System.out.println(
MessageFormat.format("{0},释放{1}了", name, lock1Name)
);
}
}
public class deadlock {
private static final ReentrantLock lock1 = new ReentrantLock();
private static final ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) {
new Thread(new ThreadByLock("线程大A", lock1, lock2,"第一个锁","第二个锁")).start();
new Thread(new ThreadByLock("线程B",lock2,lock1,"第二个锁","第一个锁")).start();
}
}
这种方法对代码的结构有一定程度的变更,得考虑会不会对结果造成影响。
(2)第二种方法看看就行,凭感觉写的。
造成死锁的第三个因素是:已有旧锁,又想要新锁;不释放旧锁,死等新锁。
那就让线程长时间获取不到新锁时,主动释放旧锁,随机延时一段时间后重新执行一遍代码。(参考rfid标签的随机性防碰撞算法的思想)
import java.text.MessageFormat;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class ThreadByLock implements Runnable
{
public String name;
public ReentrantLock lock1;
public ReentrantLock lock2;
public String lock1Name;
public String lock2Name;
ThreadByLock(String name,ReentrantLock lock1,ReentrantLock lock2,String lock1Name,String lock2Name) {
this.name = name;
this.lock1 = lock1;
this.lock2 = lock2;
this.lock1Name = lock1Name;
this.lock2Name = lock2Name;
}
@Override
public void run()
{
lock1.lock();
System.out.println(
MessageFormat.format("{0},拿到{1}了", name, lock1Name)
);
try{
System.out.println(
MessageFormat.format("{0},开始休眠", name)
);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
MessageFormat.format("{0},休眠结束", name)
);
// 阻塞判断
System.out.println(
MessageFormat.format("{0},看看{1}拿不拿得到", name,lock2Name)
);
if(lock2.isLocked()){
System.out.println(
MessageFormat.format("{0},拿不到,过3秒再看看", name)
);
TimeUnit.SECONDS.sleep(3);
if(lock2.isLocked()){
Random ra =new Random();
int myDelay = ra.nextInt(8);
System.out.println(
MessageFormat.format("{0},还是拿不到,准备释放{1},延时{2}秒后重新执行", name,lock1Name,myDelay)
);
// 这个是lambda写法,总之目的是在重新执行之前先延时一下
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(myDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new ThreadByLock(name,lock1,lock2,lock1Name,lock2Name)).start();
}).start();
return;
}
}
lock2.lock();
System.out.println(
MessageFormat.format("{0},拿到{1}了", name, lock2Name)
);
try{
}catch (Exception e){
e.printStackTrace();
}finally {
lock2.unlock();
System.out.println(
MessageFormat.format("{0},释放{1}了", name, lock2Name)
);
}
}catch(Exception e){
e.printStackTrace();
}finally{
lock1.unlock();
System.out.println(
MessageFormat.format("{0},释放{1}了", name, lock1Name)
);
}
}
}
public class deadlock {
private static final ReentrantLock lock1 = new ReentrantLock();
private static final ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) {
new Thread(new ThreadByLock("线程大A", lock1, lock2,"第一个锁","第二个锁")).start();
new Thread(new ThreadByLock("线程B",lock2,lock1,"第二个锁","第一个锁")).start();
}
}
在两个加锁语句之间,如果花的时间越久,随机延时的冲突会越严重。