美文网首页Java核心技术
Java核心技术-并发编程-锁

Java核心技术-并发编程-锁

作者: Lehman_Tong | 来源:发表于2020-10-13 22:36 被阅读0次

    日常说到高并发往往针对共享资源进行读写操作很容易得到错误的结果,这个时候就需要应用到各种个样的锁,本文通过4种锁进行分享:

    • synchronized
    • ReentrantLock(可重入锁)
    • ReentrantReadWriteLock(读写锁)
    • StampedLock(戳锁)

    Part-1:synchronized

    • 同步代码块
    public void testSynchronizedCode() {
        synchronized (lockObject) {
            System.out.println("同步代码块");
        }
    }
    
    运行:javap -verbose testSynchronized.class
    
    public void testSynchronizedCode();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #3                  // Field lockObject:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: ldc           #9                  // String 同步代码块
        12: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        15: aload_1
        16: monitorexit
        17: goto          25
        20: astore_2
        21: aload_1
        22: monitorexit
        23: aload_2
        24: athrow
        25: return
      Exception table:
         from    to  target type
             7    17    20   any
            20    23    20   any
    

    进入代码块之前先通过monitorenter获取monitor锁,如果代码块执行过程中没有异常通过monitorexit释放monitor锁,
    异常则通过monitorexit锁释放monitor锁

    • 同步方法块
    public synchronized void testSynchronizedMethod() {
        System.out.println("同步方法");
    }
    
    运行:javap -verbose testSynchronized.class
    
    public synchronized void testSynchronizedMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #7                  // String 同步方法
         5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 18: 0
        line 19: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Llsf/study/testSynchronized;
    

    Part-2:ReentrantLock可重入锁

    // 参数fair 代表是否公平锁,默认为false
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    //重入锁配合Condition进行使用实现等待功能
    private final ReentrantLock reentrantLock = new ReentrantLock(true);
    private final Condition addCondition = reentrantLock.newCondition();
    private final Condition subCondition = reentrantLock.newCondition();
    private int count = 0;
    
    public void add() {
        reentrantLock.lock();
        try {
            while (count >= 10) {
                System.out.println("加法-等待区");
                addCondition.await();
            }
            count++;
            System.out.println("加---结果:" + count);
            subCondition.signal();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }
    
    public void sub() {
        reentrantLock.lock();
        try {
            while (count <= 0) {
                System.out.println("减法-等待区");
                subCondition.await();
            }
            count--;
            System.out.println("减---结果:" + count);
            addCondition.signal();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }
    

    Part-3:ReentrantReadWriteLock可重入读写锁

    • 锁升级:ReentrantReadWriteLock中不支持
    ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
    reentrantReadWriteLock.readLock().lock();
    System.out.println("读锁获取完毕");
    reentrantReadWriteLock.writeLock().lock();
    System.out.println("写锁获取完毕");
    运行结果:
    读锁获取完毕
    
    • 锁降级:释放锁的顺序可以调整
    ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
    reentrantReadWriteLock.writeLock().lock();
    System.out.println("写锁获取完毕");
    reentrantReadWriteLock.readLock().lock();
    System.out.println("读锁获取完毕");
    reentrantReadWriteLock.writeLock().unlock();
    System.out.println("写锁释放完毕");
    reentrantReadWriteLock.readLock().unlock();
    System.out.println("读锁释放完毕");
    reentrantReadWriteLock.writeLock().lock();
    System.out.println("写锁获取完毕");
    
    运行结果:
    写锁获取完毕
    读锁获取完毕
    写锁释放完毕
    读锁释放完毕
    写锁获取完毕
    

    总结如下:读读共享、写读互斥,读写互斥、写写互斥

    Part-4:StampedLock戳锁

    StampedLock和ReadWriteLock相比,改进读的过程中也允许获取写锁后写入!我们读的数据就可能不一致,
    需要额外代码判断读的过程中是否有写入,这种读锁是一种乐观锁。
    通过StampedLocked类提供样例代码进行分析

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();
    
        void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }
    
        double distanceFromOrigin() { // A read-only method
            long stamp = sl.tryOptimisticRead();// 尝试获取乐观锁
            double currentX = x, currentY = y;
            if (!sl.validate(stamp)) {  //判断读的过程中是否有写入,如果没有则没有锁
                stamp = sl.readLock();  // 获取读锁
                try {
                    currentX = x;
                    currentY = y;
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }
    
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // Could instead start with optimistic, not read mode
            long stamp = sl.readLock();
            try {
                while (x == 0.0 && y == 0.0) {
                    long ws = sl.tryConvertToWriteLock(stamp);//尝试转化为写锁
                    if (ws != 0L) {
                        // 升级成功,更新锁戳标,退出循环
                        stamp = ws;
                        x = newX;
                        y = newY;
                        break;
                    } else {
                        // 读锁升级写锁失败,显示获取独占锁,循环重试
                        sl.unlockRead(stamp);
                        stamp = sl.writeLock();
                    }
                }
            } finally {
                sl.unlock(stamp);
            }
        }
    }
    

    ReentrantReadWriteLock其他线程尝试获取写锁的时候,会被阻塞,
    StampedLock在乐观获取锁后,其他线程尝试获取写锁,也不会被阻塞,这其实是对读锁的优化,
    在获取乐观读锁后,还需要对结果进行校验。

    Part-5:总结

    • Synchronized:在日常使用种无需关注锁的释放,并且是原生内容后期优化空间很大,一般开发种也最常用
    • ReentrantLock:锁的细粒度和灵活度,可以指定锁分配策略(公平/非公平)
    • ReentrantReadWriteLock:在读多写少的场景比较合适
    • StampedLock:Jdk1.8版本新增功能,性能优于ReentrantReadWriteLock

    参考资料

    相关文章

      网友评论

        本文标题:Java核心技术-并发编程-锁

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