Java-第十六部分-JUC-读写锁和阻塞队列

238 阅读3分钟

JUC全文

读写锁

  • 一个资源可以被多个读线程访问,被一个写线程访问,但是不能同时存在读写线程读写互斥读读共享

乐观锁

  • 支持并发,通过版本号进行保证数据准确,以先提交版本号的为主

悲观锁

  • 不支持并发操作,效率很低

表锁

  • 操作一条记录时,对整个表都上锁

行锁

  • 只对一条记录上锁
  • 存在发生死锁的可能

读锁

  • 共享锁
  • 存在发生死锁的可能

两个线程对同一个资源进行读并修改,A需要等B读完才能进行修改,而B需要等A读完才能进行修改

写锁

  • 独占锁
  • 存在发生死锁的可能

两个线程同时对一个资源进行写操作,A先修改第一条记录再修改第二条记录,B先修改第二条记录再修改第一条记录,AB互相等待

读写锁案例

class MyCache {
    private volatile Map<String, String> map = new HashMap<>();
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();
    //向map放
    void put(String key, String val) {

        try {
            rwLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "正在写 - " + key);
            TimeUnit.MILLISECONDS.sleep(100);
            map.put(key,val);
            System.out.println(Thread.currentThread().getName() + "写完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }

    }
    //取数据
    String get(String key) {
        rwLock.readLock().lock();
        String s = null;
        try {
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println(Thread.currentThread().getName() + "正在取 - " + key);
            s = map.get(key);
            System.out.println(Thread.currentThread().getName() + "取完成 - " + s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
        return s;
    }
}
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 0; i < 5; i++) {
            final int num = i;
            new Thread(() -> {

                myCache.put(num + "", Thread.currentThread().getName() + " - " + num);

            }, "Thread" + i).start();
        }
        for (int i = 0; i < 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num + "");
            }, "Thread" + i + 10).start();
        }
    }
}

演变

  • 无锁

多线程抢夺资源

  • synchronized或者ReentrantLock

独占式,每次只能来一个操作,资源读读不能共享

  • 读写锁 ReentrantReadWriteLock
  1. 读读共享,同时可以让多个线程读
  2. 很容易造成锁饥饿,一直读会导致没有写操作。
  3. 读的时候,不能写,只有读完成之后,才可以写;写操作中可以有读操作;读的时候哪个线程都不能写,不能保证数据准确
  4. 不同线程,只能存在读锁;同一线程,可以有写锁和读锁,先释放写锁,继续持有读锁

锁降级

  • 将写入锁降级为读锁
  • 过程
  1. 获取写锁
  2. 获取读锁
  3. 释放写锁
  4. 读操作,释放读锁
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
rw.writeLock().lock();
System.out.println("rw.writeLock().lock();");
rw.readLock().lock();
System.out.println("rw.readLock().lock();");
rw.readLock().unlock();
rw.writeLock().unlock();

阻塞队列

  • 队列,先进先出
  • 栈,后进先出
  • 通过共享队列,使得数据由队列一段输入,另一端输出
  1. 队列为空,从队列中获取元素的操作会被阻塞
  2. 队列是满的,从队列中添加元素的操作会被阻塞
  3. 试图从空的队列获取元素的线程会被阻塞,直到其他线程往空的队列插入新的元素
  4. 试图向已满的队列中添加新元素的线程会被阻塞,直到其他线程从队列中移除元素或完全清空
  • 阻塞,线程挂起,当可以操作时,又可以自动被唤起

基本架构

  • ArrayBlockingQueue,由数组结构组成的有界阻塞队列

维护一个定长的数组,缓存队列中的数据对象

BlockingQueue<String> bq = new ArrayBlockingQueue<>(3);
//如果满了,添加,会抛异常
System.out.println(bq.add("a"));
System.out.println(bq.add("b"));
System.out.println(bq.add("c"));
System.out.println(bq.element());
//如果空了,移除,会抛异常
System.out.println(bq.remove());
//移除指定
System.out.println(bq.remove("c"));

//如果满了添加,会返回false
System.out.println(bq.offer("a"));
System.out.println(bq.offer("b"));
System.out.println(bq.offer("c"));
//查看顶部元素
System.out.println(bq.peek());
//如果空了移除,会返回false
System.out.println(bq.poll());
//等待三秒,值,时间值,单位
System.out.println(bq.offer("a", 3L, TimeUnit.SECONDS));

//放不进去会阻塞,等待能放
bq.put("a");
//队列为空,取不到,会阻塞
System.out.println(bq.take());
  • LinkedBlockQueue,由链表结构组成的有界阻塞队列,但有大小为默认值Integer.MAX_VALUE
  • DelayQueue,优先级队列实现的延迟无界阻塞队列

只有当指定的延迟时间到了,才能够从队列中获取到该元素

  • PriorityBlockingQueue,支持优先级排序的无界阻塞队列

内部采用公平锁

  • SynchronousQueue,不存储元素的阻塞队列,只能有单个元素
  • LikedTransferQueue,由链表组成的无界阻塞队列
  • LinkedBlockingDeque,由链表组成的双向阻塞队列