美文网首页服务端开发实战java面试
死锁产生的原因和解锁的方法

死锁产生的原因和解锁的方法

作者: AKyS佐毅 | 来源:发表于2018-09-05 22:54 被阅读13次

死锁产生的原因和解锁的方法

  • 1、产生死锁的四个必要条件:

    • 互斥条件:一个资源每次只能被一个进程使用。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

举例说明

/**
 * 一个简单的死锁类
 * 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒
 * 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒
 * td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;
 * td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;
 * td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
 */

@Slf4j
public class DeadLock implements Runnable {
    public int flag = 1;
    //静态对象是类的所有对象共享的
    private static Object o1 = new Object(), o2 = new Object();

    @Override
    public void run() {
        log.info("flag:{}", flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    log.info("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    log.info("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock td1 = new DeadLock();
        DeadLock td2 = new DeadLock();
        td1.flag = 1;
        td2.flag = 0;
        //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
        //td2的run()可能在td1的run()之前运行
        new Thread(td1).start();
        new Thread(td2).start();
    }
}
  • 2、如何避免死锁

    • 1、死锁检测和恢复(deadlock detection and recovery)

      死锁检测(deadlock detection)即探查和识别死锁的方法。这种策略并不采取任何动作来使死锁不出现,而是系统事件触发执行一个检测算法。,也即在系统运行过程中,及时地探查和识别死锁的存在,并识别出处于死锁之中的进程和资源等。死锁恢复(deadlock recovery)是指当检测并识别出系统中出现处于死锁之中的一组进程时,如何使系统回复到正常状态并继续执行下去。死锁恢复常采用下述两种方法。

    • 1、 撤消进程即当发现死锁时,就撤消(夭折)一些处于死锁状态的进程,并收回它的占用的资源,以解除死锁,使其它进程能继续运行,或者在提供检查点(checkpoint)信息情况下回退(rolled back)到一个较早的状态。这里有一个开销问题,即撤消哪个(些)进程比较“划算”。

    • 2、 挂起进程即当发现死锁时,挂起一些进程,抢占它们占用的资源,使得处于死锁之中的其它进程继续执行。待以后条件满足后,再恢复被挂起的进程。

常利用资源分配图、进程等待图来协助这种检测。

  • 2、死锁预防(deadlock prevention)
 - 1、死锁预防(deadlock prevention)是在系统运行之前,事先考虑防止死锁发生的对策,即在最初设计各种资源调度算法时,就没法防止在系统运行过程中可能产生的死锁。Coffmman 等人[1971]曾提出进程在利用可重用性资源时产生死锁的四个必要条件:

    - 1、 **互斥使用(mutual exclusion)**:系统中存在一次只能给一个进程使用的资源。

    - 2、**占用并等待(resource holding and waiting)**:系统中存在这样的进程,它(们)已占有部分资源并等待得到另外的资源,而这些资源又被其它进程所占用还未释放。

    - 3、**非抢占分配(nonpreemption)**:资源在占有它的进程自愿交出之前,不可被其它进程所强行占用。

    - 4、**部分地分配(partial allocation)或循环等待(circular waiting)**:存在一个两个或都个进程的循环链,链中每一个进程等待被链中下一个进程占有的资源。即在一定条件下,若干进程进入了相互无休止地等待所需资源的状态。

所有这四个必要条件都应该在死锁时出现。

 - 1、每个进程必须一次性地请求它所需要的所有资源。若系统无法满足这一要求,则它不能执行。这是一种预分资源方法,它破坏了产生死锁的第三个必要条件。
 - 2、一个已占有资源的进程若要再申请新资源,它必须先释放已占资源。若随后它还需要它们,则需要重新提出申请。换言之,一个进程在使用某资源过程中可以放弃该资源,从而破坏了产生死锁的第二个必要条件。
 - 3、将系统中所有资源顺序编号,规定进程只能依次申请资源,这就是说,一个进程只有在前面的申请满足后,才能提出对其后面序号的资源的请求。这是一种有序使用资源法,它破坏了产生死锁的第四个必要条件。
  • 3、死锁避免(deadlock avoidence)
     
       死锁避免(deadlock avoidence)是在系统运行过程中注意避免死锁的发生。这就要求每当申请一个资源时,系统都应根据一定的算法判断是否认可这次申请,使得在今后一段时间内系统不会出现死锁。

2、对象锁和类锁是否会互相影响?

  • 1、对象锁:Java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。

  • 2、类锁:对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。我们都知道,java类可能会有很多个对象,但是只有1个Class对象,也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以所谓的类锁,不过是Class对象的锁而已。获取类的Class对象有好几种,最简单的就是MyClass.class的方式。

  • 3、类锁和对象锁不是同1个东西,一个是类的Class对象的锁,一个是类的实例的锁。也就是说:1个线程访问静态synchronized的时候,允许另一个线程访问对象的实例synchronized方法。反过来也是成立的,因为他们需要的锁是不同的。

3、线程同步机制

线程同步是为了确保线程安全,所谓线程安全指的是多个线程对同一资源进行访问时,有可能产生数据不一致问题,导致线程访问的资源并不是安全的。如果多线程程序运行结果和单线程运行的结果是一样的,且相关变量的值与预期值一样,则是线程安全的。

Java中与线程同步有关的关键字/类包括:

volatile、synchronized、Lock、AtomicInteger等concurrent包下的原子类。。。等

  • 1、volatile

volatile一般用在多个线程访问同一个变量时,对该变量进行唯一性约束,volatile保证了变量的可见性,不能保证原子性。

用法(例):

private volatile booleanflag = false

  • 保证变量的可见性:volatile本质是告诉JVM当前变量在线程寄存器(工作内存)中的值是不确定的,需要从主存中读取,每个线程对该变量的修改是可见的,当有线程修改该变量时,会立即同步到主存中,其他线程读取的是修改后的最新值。

  • 不能保证原子性:原子性指的是不会被线程调度机制打断的操作,在java中,对基本数据类型的变量的读取和赋值操作是原子性操作。自增/自减操作不是原子性操作。例如:i++,其实是分成三步来操作的:1)从主存中读取i的值;2)执行+1操作;3)回写i的值。volatile关键字并不能保证原子性操作。非原子操作都会产生线程安全的问题,那么如何实现自增/自减的原子性呢?后续将有讲解。

  • 2、synchronized

    • synchronized提供了一种独占的加锁方式,是比较常用的线程同步的关键字,一般在“线程安全的单例”中普遍使用。该关键字能够保证代码块的同步性和方法层面的同步。

    • 代码块同步

    • 方法同步

    //使用synchronized关键字实现线程安全的单例模式   代码块同步
    private static Singleton instance;
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class)
            {
                if(instance == null){
                    instance = new Singleton();
                }              
            }
        }
        return instance;
    }
    privateSingleton(){ }
    
    //方法同步
    public static synchronized Singleton getInstance2(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
     }
    private Singleton(){ }
    
  • volatile和synchronized的区别

    • volatile通过变量的可见性,指定线程必须从主存中读取变量的最新值;synchronized通过阻塞线程的方式,只有当前线程能访问该变量,锁定了当前变量。

    • volatile使用在变量级别;synchronized可以使用在变量、方法、类级别

    • volatile不会造成线程阻塞;synchronized可能会造成线程阻塞

    • volatile不能保证原子性;synchronized能保证原子性

    • volatile标记的变量不会被编译器优化;synchronized标记的变量有可能会被编译器优化(指令重排)。

  • 3、如何保证自增/自减的原子性

  • 使用java.util.concurrent包下提供的原子类,如AtomicIntegerAtomicLongAtomicReference等。用法:

AtomicInteger atomicInteger = new AtomicInteger();  
atomicInteger.getAndIncrement();//实现原子自增  
atomicInteger.getAndDecrement();//实现原子自减  
  • 2、使用synchronized同步代码块
Synchronized(this){  
    value++;  
}  
  • 3、使用Lock显示锁同步代码块
private Lock lock = new ReentrantLock();  
 private final int incrementAndGet(){  
     lock.lock();  
     try  {  
         return value++;  
     }  finally  {  
         // TODO: handle finally clause  
         lock.unlock();  
     }  
 }  

4、如何保证多线程读写文件的安全

  • 沉睡唤醒机制:

    • 需求:两个线程写一个TXT文件,线程1:I love you 线程2:so do I 。保证线程1、2有序执行,一句I love you,对应一句so do I。
    • 第一步,先创建WRFile类。这一步是关键的。
   public class WRFile {
         //String str;
         boolean flag;
         public WRFile(){
             
         }
         
         public synchronized void read1(){
             if(this.flag){
                 try {
                     this.wait();
                 } catch (InterruptedException e) {
                     
                     e.printStackTrace();
                 }
             }
             RandomAccessFile ra = null;
             try {
                 ra = new RandomAccessFile("love.txt", "rw");
                 ra.seek(ra.length());
             
                 ra.writeBytes("I love you");
                 ra.writeBytes("\r\n");
             } catch (Exception e) {
                 e.printStackTrace();
             }
             finally {
                 try {
                     ra.close();
                 } catch (IOException e) {
                     
                     e.printStackTrace();
                 }
             }
             //修改标记 唤醒线程
             this.flag = true;
             this.notify();
         }
         
         public synchronized void read2(){
             if(!this.flag){
                 try {
                     this.wait();
                 } catch (InterruptedException e) {
                     
                     e.printStackTrace();
                 }
             }
             RandomAccessFile ra = null;
             try {
                 ra = new RandomAccessFile("love.txt", "rw");
                 ra.seek(ra.length());
             
                 ra.writeBytes("so do I");
                 ra.writeBytes("\r\n");
             } catch (Exception e) {
                 
                 e.printStackTrace();
             } finally {
                 try {
                     ra.close();
                 } catch (IOException e) {
                     
                     e.printStackTrace();
                 }
             }
             //修改标记 唤醒线程
             this.flag = false;
             this.notify();
         }

}
  • 第二步,创建我们的两个线程类,第一个FirstThread。
public class FirstThread implements Runnable {
      private WRFile wr = new WRFile();
      
         public FirstThread(WRFile wr) {
              this.wr = wr;
          } 
          
          @Override
          public void run() {
              
              while(true){
                  wr.read1();
              }
         }
  }
public class SecondThrad implements Runnable{
      private WRFile wr = new WRFile();
      public SecondThrad(WRFile wr) {
          this.wr = wr;
      }
      @Override
      public void run() {
          while(true)
          {
              wr.read2();
          }
      }
}
  • 第三步:
public static void main(String[] args) {
      //创建数据对象
      WRFile wr = new WRFile();
      //设置和获取类
      FirstThread ft = new FirstThread(wr);
      SecondThrad st = new SecondThrad(wr);
      //线程类
      Thread th1 = new Thread(ft);
      Thread th2 = new Thread(st);
      //启动线程
      th1.start();
      th2.start();
  }

145天以来,Java架构更新了 428个主题,已经有91位同学加入。微信扫码关注java架构,获取Java面试题和架构师相关题目和视频。上述相关面试题答案,尽在Java架构中。

相关文章

  • 死锁产生的原因和解锁的方法

    死锁产生的原因和解锁的方法 1、产生死锁的四个必要条件:互斥条件:一个资源每次只能被一个进程使用。请求与保持条件:...

  • 死锁产生的原因和解锁的方法

    产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请...

  • 死锁产生的原因和解锁的方法

    死锁产生的原因和解锁的方法 产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) ...

  • iOS 死锁案例和产生的原因

    iOS 死锁案例和产生的原因 上面的代码会打印什么呢?答案是死锁 死锁的原因是由于队列引起的循环等待: 我们在主队...

  • java多线程笔记

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

  • 什么是死锁,产生死锁的原因

    什么是死锁? 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用...

  • 进程产生死锁的原因

    死锁的原因主要是: (1)因为系统资源不足; (2) 资源分配不当; (3) 进程运行推进的顺序不合适等。 如果系...

  • GCD总结1

    产生死锁原因:使用sync函数往当前串行队列里面添加任务,会卡住当前串行队列(产生死锁)。

  • 死锁的原因,条件和解决办法

    死锁概念和产生原因 死锁是指多个进程循环等待彼此占有的资源而无限期的僵持等待下去的局面。原因是: 系统提供的资源太...

  • 什么是死锁?死锁产生的原因有哪些?

    1.什么是死锁死锁是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象2.死锁产生的原因①...

网友评论

    本文标题:死锁产生的原因和解锁的方法

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