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<Object> list=new ArrayList<>();
- synchronized boolean apply(Object from,Object to){
- if(list.contains(from)||list.contains(to)){
- return false;
- }
- list.add(from);
- list.add(to);
- return true;
- }
-
- synchronized void free(Object from,Object to){
- list.remove(from);
- list.remove(to);
- }
- }
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.除此之外,我们还可以破坏循环等待条件,每个都是按照顺序进行加锁等等。