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

日常说到高并发往往针对共享资源进行读写操作很容易得到错误的结果,这个时候就需要应用到各种个样的锁,本文通过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
参考资料
  • https://blog.takipi.com/java-8-stampedlocks-vs-readwritelocks-and-synchronized/
  • https://www.liaoxuefeng.com/wiki/1252599548343744/1309138673991714
  • JDK源码
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值