死锁

作者: 小李同学今天博学了吗 | 来源:发表于2020-08-07 13:11 被阅读0次
死锁产生的条件:

1.互斥使用:当资源被一个线程使用时,别的线程不能使用
2.不可抢占:资源请求者不能强制从资源占有手中夺取资源,资源只能由占有者主动释放
3.请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占用
4.循环等待,即线程A持有锁L1之后去申请L2锁,线程B持有L2的锁之后去申请L1的锁,两个都在等对方释放锁,即造成了循环等待的情况

种类

1.静态顺序锁,就是我们加synchronized的顺序不一致,导致加锁的对象都在等待对方释放锁

class StaticLockOrderDeadLock {
    private final Object lockA = new Object();
    private final Object lockB = new Object();
    public void a() {
        synchronized (lockA) {
synchronized (lockB) { System.out.println("function a");
} }
}
    public void b() {
        synchronized (lockB) {
synchronized (lockA) { System.out.println("function b");
} }
} }

解决方法:加多个锁的顺序一致
2.动态锁
因为调用方法所传递的参数不一样,之后会导致加锁的顺序不一样,这里的情形是A向B转账,先对A加锁,之后对B加锁,但是会存在一种情况就是A向B转账的同时B也向A转账,这时就会造成死锁

class DynamicLockOrderDeadLock {
    public void transefMoney(Account fromAccount, Account toAcco
unt, Double amount) {
        synchronized (fromAccount) {
            synchronized (toAccount) {
//...
  fromAccount.minus(amount);     
   toAccount.add(amount); //...
} }

解决方法:使用System.identifyHashCode()来定义锁的顺序,或者用对象唯一的id来决定锁的顺序

//正确的代码
class DynamicLockOrderDeadLock {
    private final Object myLock = new Object();
    public void transefMoney(final Account fromAccount, final Account toAccount, final Double amount) {
        class Helper {
            public void transfer() {
//...
fromAccount.minus(amount); toAccount.add(amount); //...
} }
int fromHash = System.identityHashCode(fromAccount); int toHash = System.identityHashCode(toAccount);
if (fromHash < toHash) {
    synchronized (fromAccount) {
    synchronized (toAccount) { 
      new Helper().transfer();
  } 
}
} else if (fromHash > toHash) {
    synchronized (toAccount) {
    synchronized (fromAccount) { new Helper().transfer();
    } 
  }
} else {
    synchronized (myLock) {
        synchronized (fromAccount) {
              synchronized (toAccount) {
          } 
    }
    }
    } 
 }

3.协作对象之间发生的死锁
即在锁方法里面调用外部方法

public synchronized void setLocation(Point location) { 
      this.location = location;
    if (location.equals(destination))
      dispatcher.notifyAvailable(this);//外部调用方法,可能等 待Dispatcher对象锁
}
正确的代码
public void setLocation(Point location) {
        boolean flag = false;
        synchronized (this) {
               this.location = location;
          flag = location.equals(destination);
     }
    if (flag) dispatcher.notifyAvailable(this);//使用开放调用
}

解决方法:避免在持有锁的情况下调用外部方法

Synchronized关键字(可重入互斥锁)

为什么synchronized可以实现同步?
java中的每一个对象都可以作为锁,当线程访问同步代码时需要先获得对象锁,退出时或异常抛出时释放锁
原理:每个对象都有一个Moniter关联,也就是跟JVM有关,jvm自动释放锁


截屏2020-08-04 上午9.15.47.png

表现:同步代码块和方法同步

public class synchronizedTest implements Runnable {
    static synchronizedTest instance=new synchronizedTest();
    public void run() {
        synchronized(instance){ 
            //同步代码块,对应文章中第3点
            //*******
        }
    }
   void synchronized method1() {} //类中的同步方法 对应文章中第1点
   void static synchronized method2() {} ////类中静态同步方法 ,相当于对类加锁
}

1.锁住对象
a. public synchronized void method1
b. synchronized(this){ //TODO }
2.锁住类,即当前对象的Class
a.public synchronized static void method3
b. synchronized(Test.class){ //TODO}
c. synchronized(o) {}(代码块同步,这里面的o可以是任何对象)

ReentrantLock锁(可重入互斥锁)

特点:获取锁与释放锁需要自己操作,还可以中断获取锁以及超时获取锁
Lock接口的主要方法
1.void lock(): 执行此方法时,如果锁处于空闲状态,当前线程将获取到锁。相 反,如果锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁。
2.boolean tryLock(): 如果锁可用,则获取锁,并立即返回true,否则返回 false. 该方法和lock()的区别在于,tryLock()只是"试图"获取锁,如果锁不可 用,不会导致当前线程被禁用,当前线程仍然继续往下执行代码。而lock()方法 则是一定要获取到锁,如果锁不可用,就一直等待,在未获得锁之前,当前线程 并不继续向下执行. 通常采用如下的代码形式调用tryLock()方法:
3.void unlock(): 执行此方法时,当前线程将释放持有的锁. 锁只能由持有者释 放,如果线程并不持有锁,却执行该方法,可能导致异常的发生.
4.Condition newCondition(): 条件对象,获取等待通知组件。该组件和当前的 锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后, 当前线程将缩放锁。

使用:

ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁 .....................
          lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
        try {
          //操作
          } finally {
            lock.unlock(); //释放锁 
          }
区别

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的 语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现 象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造 成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用 synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。

  1. sychronized是非公平锁,reentrantLock是公平锁
线程间通信的两种方式

1.wait()/notify()
Object类中相关的方法有notify方法和wait方法。因为wait和notify方法定义在Object 类中,因此会被所有的类所继承。这些方法都是final的,即它们都是不能被重写 的,不能通过子类覆写去改变它们的行为。

1wait()方法: 让当前线程进入等待,并释放锁。
2wait(long)方法: 让当前线程进入等待,并释放锁,不过等待时间为long,超过这个时间没有对当前线程进行唤醒,将自动唤醒。
3notify()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕后 释放锁,并从其他线程中唤醒其中一个继续执行。
4notifyAll()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕 后释放锁,将唤醒所有等待状态的线程。

wait()方法使用注意事项:
1当前的线程必须拥有当前对象的monitor,也即lock,就是锁,才能调用wait()方
法,否则将抛出异常java.lang.IllegalMonitorStateException。
2线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知 的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执 行。
3要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在 synchronized方法或synchronized块中。

notify()方法使用注意事项
1如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具
体实现有关。(线程等待一个对象的锁是由于调用了wait()方法)。
2被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁,当前线程会在方法执行完毕后释放锁。

wait()与sleep()的比较
当线程调用了wait()方法时,它会释放掉对象的锁。
Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会 释放掉对象的锁的。

相关文章

  • 死锁

    线程饥饿死锁 锁顺序死锁 动态锁顺序死锁通过锁顺序来避免死锁 避免死锁

  • 死锁

    第11章:死锁和进程通信 死锁概念 死锁处理方法 死锁预防(Deadlock Prevention) 死锁避免(D...

  • java多线程笔记

    产生死锁的四个必要条件 处理死锁的基本方法 死锁预防 死锁避免 死锁检测 死锁解除 https://blog.cs...

  • [现代操作系统]--死锁

    table of content 死锁定义 死锁建模-- 资源分配图 处理死锁鸵鸟算法检测并恢复死锁检测死锁恢复利...

  • Java-多线程(四)死锁

    死锁 死锁示例

  • Java死锁

    什么是死锁 死锁检测 产生死锁的四个必要条件 如何避免死锁 死锁 死锁,指两个或多个线程之间,由于互相持有对方需要...

  • java并发--java死锁

    本篇结构: 前言 什么是死锁 产生死锁的必要条件 死锁的代码示例 死锁排查 如何避免死锁 总结 一、前言 今天被问...

  • Java多线程之死锁(Deadlock)及死锁避免(Deadlo

    线程死锁(Thread Deadlock) 数据库死锁(Database Deadlocks) 死锁避免 (Dea...

  • JavaEE面试题总结 Day39 2018-12-29

    什么是线程死锁?死锁如何产生?如何避免线程死锁? 死锁的介绍: 线程死锁是指由于两个或者多个线程互相持有对方所需要...

  • Java并发之嵌套管程锁死(Nested Monitor Loc

    嵌套管程死锁是如何发生的 具体的嵌套管程死锁的例子 嵌套管程死锁 vs 死锁 嵌套管程锁死类似于死锁, 下面是一个...

网友评论

      本文标题:死锁

      本文链接:https://www.haomeiwen.com/subject/qfvprktx.html