• 【并发编程】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()。

  • 相关阅读:
    PAT (Advanced Level) 1080. Graduate Admission (30)
    PAT (Advanced Level) 1079. Total Sales of Supply Chain (25)
    PAT (Advanced Level) 1078. Hashing (25)
    PAT (Advanced Level) 1077. Kuchiguse (20)
    PAT (Advanced Level) 1076. Forwards on Weibo (30)
    PAT (Advanced Level) 1075. PAT Judge (25)
    PAT (Advanced Level) 1074. Reversing Linked List (25)
    PAT (Advanced Level) 1073. Scientific Notation (20)
    PAT (Advanced Level) 1072. Gas Station (30)
    PAT (Advanced Level) 1071. Speech Patterns (25)
  • 原文地址:https://www.cnblogs.com/shinyrou/p/13300983.html
Copyright © 2020-2023  润新知