zoukankan      html  css  js  c++  java
  • 【并发编程】4.JUC中常用的锁

    JUC即java.util.concurrent的简称,在这个包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架,还提供了设计用于多线程上下文中。通过她们能够很好地帮助我们在开发中提高一些程序的性能。

    1.Lock与Condition

    • condition
      Lock与condition是Java中管程模型除了synchronized的另一套实现,不同是的支持多条件队列。Lock&Condition 实现的管程里只能使用前面的 await()、signal()、signalAll()。
      synchronized的管程模型

      Lock&Condition的管程模型

    Condition的使用 需要获取到锁

    public class BlockedQueue<T>{
        final Lock lock =
                new ReentrantLock();
        // 条件变量:队列不满
        final Condition notFull =
                lock.newCondition();
        // 条件变量:队列不空
        final Condition notEmpty =
                lock.newCondition();
        // 入队
        void enq(T x) {
            lock.lock();
            try {
                while (队列已满){
                    // 等待队列不满
                    notFull.await();
                }
                // 省略入队操作...
                // 入队后, 通知可出队
                notEmpty.signal();
            }finally {
                lock.unlock();
            }
        }
        // 出队
        void deq(){
            lock.lock();
            try {
                while (队列已空){
                    // 等待队列不空
                    notEmpty.await();
                }
                // 省略出队操作...
                // 出队后,通知可入队
                notFull.signal();
            }finally {
                lock.unlock();
            }
        }
    }
    

    上述代码中就是有两个条件变量,对应两个条件队列,分别进行入队和出队。 队列已满通知等待出队操作的线程,队列已空则通知等待入队操作的线程。
    如果是使用synchronized关键字实现的话调用notify/notifyAll方法应该是通知唯一的等待队列里的所有线程,然后判断是执行入队或者是出队。

    • ReentrantLock
      上述代码中使用的锁就是 ReentrantLock 即为可重入锁,就是再已经获取锁的情况下可以再次获取到同一把锁。
    class X {
        private final Lock rtl = new ReentrantLock();
        int value;
        public int get() {
            // 获取锁
            rtl.lock(); 
            try {
                return value;
            } finally {
                // 保证锁能释放
                rtl.unlock();
            }
        }
        public void addOne() {
            // 获取锁
            rtl.lock();
            try {
                value = 1 + get(); //get方法再次获取锁
            } finally {
                // 保证锁能释放
                rtl.unlock();
            }
        }
    }
    
    • ReadWriteLock
      读写锁,适用于读多写少的场景,例如缓存
      ReadWriteLock 是接口
      ReentrantReadWriteLock 是实现类
    1. 允许多个线程同时读共享变量; (优于互斥锁的关键) 如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。
    2. 只允许一个线程写共享变量;
    3. 如果一个写线程正在执行写操作,此时禁止读线程读共享变量 (区别于对读操作不加锁)
    /**
    * className: Cache
    * create by: zhujun
    * description: 使用读写锁实现的缓存工具类
    * 读写锁 使用于读多写少的并发场景
    * create time: 2019/7/23 11:21
    */
    public class Cache<K,V> {
        //hashmap 存储数据
        final HashMap<K,V> hashMap = new HashMap<>();
        final ReentrantReadWriteLock reentrantReadWriteLock  = new ReentrantReadWriteLock();
        final Lock readLock = reentrantReadWriteLock.readLock();
        final Lock writeLock = reentrantReadWriteLock.writeLock();
    
        /**
         * 存入数据
         * @param key
         * @param value
         */
        void set(K key,V value){
            writeLock.lock();
            try{
                hashMap.put(key,value);
            }finally {
                writeLock.unlock();
            }
        }
    
        /**
         * 读取数据
         * @param key
         * @return
         */
        V get(K key){
          //读锁
          readLock.lock();
          try{
              return hashMap.get(key);
          }finally {
              readLock.unlock();
          }
        }
    }
    

    注意点:1.写锁支持条件变量,读锁不支持条件变量
    2.支持锁的降级,不支持锁的升级
    锁的升级

            read.lock();
            try {
                v = m.get(key);//验证值是否存在         
                //获取写锁从数据库中更新缓存                       //锁的升级
            } finally{
                read.unlock();
            }
    

    锁的降级

          writeLock.lock();
          try{
                  ....
              readLock.lock();//释放写锁之前 释放读锁
          }finally{
              writeLock.unlock();
              readLock.lock()
          }
    
    • StampedLock
      Java 1.8提供,性能优于ReadWriteLock 支持三种锁模式:写锁,乐观读锁,悲观读锁。
      因为 ReadWriteLock 是悲观读锁,读取的时候不允许写入,StampedLock为了提高性能提供了乐观读锁,读的过程中大概率不会有写入。

    首先我们通过tryOptimisticRead()获取一个乐观读锁,并返回版本号。
    接着进行读取,读取完成后,我们通过validate()去验证版本号,如果在读取过程中没有写入,版本号不变,验证成功,我们就可以放心地继续后续操作。
    如果在读取过程中有写入,版本号会发生变化,验证将失败。在失败的时候,我们再通过获取悲观读锁再次读取。

    /**
    * className: Point
    * create by: zhujun
    * description:StampLock 的乐观读与悲观读锁
    * create time: 2019/7/30 17:01
    */
    public class Point {
        private int x;
        private int y;
    
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        final StampedLock s1 = new StampedLock();
    
    
        //计算带到原点的举例
        double getDistance() throws InterruptedException {
           long stamp = s1.tryOptimisticRead();//乐观读
            System.out.println("乐观读stamp:"+stamp);
            int curX = x;
            int cutY = y;
            System.out.println("读取成功:"+x+","+y);
            Thread.sleep(1000);//睡眠 方便测试时进行写操作
           if(!s1.validate(stamp)){//通过验证stamp
                //期间有写操作 升级为悲观读锁 等待写操作完成
               stamp =  s1.readLock();
                try{
                    System.out.println("存在写操作,重新读取x,y坐标");
                    System.out.println("此时stamp:"+stamp);
                    curX = x;
                    cutY = y;
                }finally {
                    s1.unlockRead(stamp);
                }
           }
           return  Math.sqrt(curX*curX+cutY*cutY);
        }
    
    
    
        void reLocation(int x,int y){
            //写操作 写锁
            long stamp = s1.writeLock();
            System.out.println("写锁stamp:"+stamp);
            try{
                this.x = x;
                this.y = y;
                System.out.println("重定位成功:"+x+","+y);
            }finally {
                s1.unlockWrite(stamp);
            }
        }
    }
    
    public class PointTest {
        public static void main(String[] args) throws InterruptedException {
            Point p = new Point(1,2);
            //线程1 计算距离
            Thread th1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        double distance = p.getInstance();
                        System.out.println("距离原点的举例:"+distance);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            //线程2 重写坐标
            Thread th2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    p.reLocation(3,4);
                }
            });
            th1.start();
            th2.start();
        }
    }
    

    注意:1.StampedLock 不支持可重入
    2.如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁 writeLockInterruptibly()。

  • 相关阅读:
    Python 容器用法整理
    C/C++中浮点数格式学习——以IEEE75432位单精度为例
    关于C/C++中的位运算技巧
    [GeekBand] C++11~14
    [GeekBand] 探讨C++新标准之新语法——C++ 11~14
    [GeekBand] 面向对象的设计模式(C++)(2)
    [GeekBand] 面向对象的设计模式(C++)(1)
    [GeekBand] STL与泛型编程(3)
    [GeekBand] STL与泛型编程(2)
    [GeekBand] STL与泛型编程(1)
  • 原文地址:https://www.cnblogs.com/shinyrou/p/13300983.html
Copyright © 2011-2022 走看看