阻塞队列和死锁

BlockingQueue

       BlockingQueue是并发容器的一种,在J.U.C的包路径下,是线程安全的一种实现,是基于阻塞队列的,该接口提供了相对于Queue的新的put()和take()操作。put()添加元素时,当阻塞队列满的情况下会阻塞下来,当有空间时才能进行添加操作,添加到队列尾部;take()删除元素时,当队列为空时,也会阻塞,当有元素时才能进行删除操作,删除队列头部元素。该接口不接受存储null值,否则会抛出NUllPointerException的异常;BlockingQueue是可以指定容量的,如果超过了就会就行阻塞;它是线程安全的,他的实现类内部可以使用内部锁或者其他形式的并发控制来实现线程安全。

  • ArrayBlockingQueue     基于数组(环形数组,不断的进行数据的添加删除,添加和删除的采用双下标指示)实现的有界阻塞队列,数组大小是固定的,所以需要指定数组大小,内部使用一个ReentrantLock和两个Condition实例,通过ReentrantLock实现线程安全,通过Condition实例来进行添加和删除元素的阻塞和唤醒的通知;ArrayBlockingQueue队列按照先进先出原则对元素进行排序,在默认的情况下不保证元素操作的公平性。队列操作的公平性是指在生产者线程或者消费者线程发生阻塞后再次被唤醒时,按照阻塞的先后顺序就行操作队列(先阻塞的生产者线程优先向队列中插入元素,先阻塞的消费者线程优先从队列中获取元素),因为保证公平性会降低吞吐量,所以如果要处理的数据无先后顺序,便使用非公平的处理方式。
    ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(100,true);//大小为100的公平队列
    ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(100,false);//大小为100的非公平队列
  • LinkedBlockingQueue    基于链表实现的无界阻塞队列,同ArrayBlockingQueue类似,同样是按照先见先出原则对元素就行排序;但是LinkedBlockingQueue实现上采用head和last的节点是分离的,入队列和出队列想互不干扰,对入队列和出队列端分别采用了两个独立的锁来控制数据同步,可以将队列头部的锁理解为写锁,队列尾部的锁理解为读锁,因此出队列和入队列可以基于各自独立的锁进行操作队列的元素,其并发性能较高。内部使用两个ReentrankLock和两个Condition实例。
  • PriorityBlockingQueue    支持优先级的无界队列,元素在默认情况下采用自然顺序升序排列,可以自定义实现compareTo()方法来指定元素进行排序规则,或者在初始化PriorityBlockingQueue时指定构造函数Comparator来实现对元素的排序,当两个元素的优先级相同时,则不能保证该元素的存储以及访问顺序
  • SynchronousQueue     是一个不存储元素的同步阻塞队列,每个插入数据操作必须等待另一个移除操作,队列中最多只能有一个元素,每一个put操作都必须等待一个take操作完成,否则便不能向队列中添加元素。可以将其看成是“快递员”,它负责将生产者线程的数据直接传递给消费者线程,非常适用于传递型场景。
  • DelayQueue     是一个支持延时获取元素的无界阻塞队列。

 死锁

       指的是两个或者两个以上的进程在执行的过程中,由于竞争资源而造成阻塞,若无外力作用下则无法继续推进,此时称作系统的死锁状态。

死锁产生的原因:

  1. 因竞争资源而产生死锁
  2. 因进程推进顺序不当而产生死锁

死锁代码实例(两个进程竞争同一个资源):

public class DeadLockDemo {
    private static Object object = new Object();
    private static Object object1 = new Object();

    public static void main(String[] args) {
        //线程t1先获取object,再获取object1
        //线程t2先获取object1,再获取object
        Runnable t1 = new Runnable() {
            @Override
            public void run() {
                synchronized (object){
                    System.out.println("线程t1获取到资源object");
                    synchronized (object1){
                        System.out.println("线程t1获取到资源object1");
                    }
                }
            }
        };

        Runnable t2 = new Runnable() {
            @Override
            public void run() {
                synchronized (object1){
                    System.out.println("线程t2获取到资源object1");
                    synchronized (object){
                        System.out.println("线程t2获取到资源object");
                    }
                }
            }
        };
//线程启动
        new Thread(t1).start();
        new Thread(t2).start();
    }
}

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

  1. 互斥条件:一个资源每次只能被一个线程使用(例如一个打印机同时只能供一个人使用)
  2. 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放

  3. 不可剥夺条件: 进程已获得的资源,在未使用之前,不能强行剩夺
  4. 循环等待条件: 若干进程之间形成一 种头尾相连接的循环等待资源关系

死锁的解除与预防(预防,避免,检测与恢复)

  •  预防死锁只需要破坏四个必要条件 
  1. 资源一次性分配(破坏请求与保持)
  2. 当线程来获取资源未满足时,需要将已占用资源释放掉(破坏不可剥夺条件)
  3. 资源有序分配,系统给每一个资源进行编号,每一个线程按照编号递增顺序请求资源,释放资源正好相反(破坏请求与保持)
  • 避免死锁
  1. 银行家算法,允许线程进行动态资源的请求,对资源进行预分配,计算资源分配的安全性,不予许系统进入到不安全的状态。
  • 检测和解除死锁
  1. 剥夺资源:从其他线程中剥夺足够的资源给死锁进程(Mysql就是采用此方法)
  2. 撤销进程:直接撒销死锁进程或者是撒销代价最小的进程,直至有足够资源可用时再执行撤销的进程

猜你喜欢

转载自www.cnblogs.com/128-cdy/p/13160863.html