synchronized同步锁,虽然能解决线程安全的问题,但是如果使用不当,就可能导致死锁,也即请求被阻塞而一直无法返回。
除了死锁,还有个活锁的情况,我们看一下概念的区别:
死锁: 一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。
活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。也就是“生不如死”的状态,不过处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
这四个条件同时满足,就会产生死锁。
互斥,共享资源 X 和 Y 只能被一个线程占用;
占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
接下来,我们构造一个简单的死锁程序来分析一下这个场景。
public class DeadLockDemo {
private static String A = "A";
private static String B = "B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
private void deadLock() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (A) {
try {
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("1");
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (B) {
try {
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
}
}
这个例子中,线程1 先给A加锁,然后就睡觉了,之后再给B加锁才能执行完。线程2,则给B加锁后就睡觉,之后再给A加锁才能执行完。所以这里两个线程就死锁了。 执行之后检验一下情况:使用jps 查看进程号,然后使用jstack 查看,结果如下:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x04ddd074 (object 0x0f39ae88, a java.lang.String),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x04dddd94 (object 0x0f39aea8, a java.lang.String),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.liuqingchao.threadTest.DeadLockDemo$2.run(DeadLockDemo.java:38)
- waiting to lock <0x0f39ae88> (a java.lang.String)
- locked <0x0f39aea8> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.liuqingchao.threadTest.DeadLockDemo$1.run(DeadLockDemo.java:22)
- waiting to lock <0x0f39aea8> (a java.lang.String)
- locked <0x0f39ae88> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
上面的内容说人话就是: 找到一个java级别的死锁: Thread1 锁了0x0f39aea8 ,等待去锁 object 0x0f39ae88,但是这个被Thread 0 持有 Thread0 锁了0x0f39ae88,等待去锁 object 0x0f39aea8,这个被 Thread1 持有 所以就死锁了。
要预防死锁,只要破坏掉其4条规则的任何一个都可以。因此我们有多种策略来解除死锁。 1.针对互斥,重写代码逻辑。调整资源分配策略,先尝试都能拿到资源再去占有,例如:
public class Allocator {
private List
2.不用synchronized,而使用ReentrantLock,加锁的时候使用fromLock.tryLock(),解决条件2 和3。
public class TransferAccount implements Runnable {
private Account fromAccount; //转出账户
private Account toAccount; //转入账户
private int amount;
Lock fromLock = new ReentrantLock();
Lock toLock = new ReentrantLock();
public TransferAccount(Account fromAccount, Account toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
@Override
public void run() {
while (true) {
if (fromLock.tryLock()) { //返回true和false
if (toLock.tryLock()) {//返回true和false
if (fromAccount.getBalance() >= amount) {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
//转出账户的余额
System.out.println(fromAccount.getName() + "->" + fromAccount.getBalance());
//转入账户的余额
System.out.println(toAccount.getName() + "->" + toAccount.getBalance());
}
}
public static void main(String[] args) {
Account fromAccount = new Account("tt", 100000);
Account toAccount = new Account("ff", 300000);
Thread a = new Thread(new TransferAccount(fromAccount, toAccount, 10));
Thread b = new Thread(new TransferAccount(toAccount, fromAccount, 30));
a.start();
b.start();
}
}
3.除此之外,我们还可以破坏循环等待条件,每个都是按照顺序进行加锁等等。