AQS同步组件ReentrantLock与锁

2次阅读

共计 4472 个字符,预计需要花费 12 分钟才能阅读完成。

ReentrantLock 与锁

Synchronized 和 ReentrantLock 异同
  • 可重入性:两者都具有可重入性
  • 锁的实现:Synchronized 是依赖 jvm 实现的,ReentrantLock 是 jdk 实现的。(我们可以理解为一个是操作系统层面的实现另一个是用户自己自己实现的)Synchronized 的实现是 jvm 层面的很难看到其中的实现。而 ReentrantLock 是通过 jvm 实现的我们可以通过阅读 jvm 源码来查看实现。
  • 性能区别:在 Synchronized 优化之前 Synchronized 的性能相比 ReentrantLock 差很多,在 Synchronized 引入了偏向锁,轻量级锁也就是自旋锁之后了,两者的性能相差不大了。在两者都可用的情况下官方更推荐使用 Synchronized,因为其写法更简单,Synchronized 的优化就是借鉴了 ReentrantLock 中的 cas 技术。
  • 功能区别:便利性,很明显 synchronized 的使用更加便利,ReentrantLock 在细粒度和灵活性中会优于 Synchronized。
ReentrantLock 独有功能
  • ReentrantLock 可指定是公平锁还是非公平锁,Synchronized 只能是非公平锁。(公平锁就是先等待的线程先获得锁)
  • ReentrantLock 提供一个 Condition 类,可以分组唤醒需要唤醒的形成。synchronized 是要嘛随机唤醒一个线程要嘛唤醒所有的线程。
  • ReentrantLock 提供了一种能够中断等待锁的线程的机制 lock.locInterruptibly(),ReentrantLock 实现是一种自旋锁通过循环调用,通过 cas 机制来实现加锁。性能较好是因为避免了线程进入内核的阻塞状态
@Slf4j
public class LockExample2 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static int count = 0;

    private final static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {executorService.execute(() -> {
                try {semaphore.acquire();
                    add();
                    semaphore.release();} catch (Exception e) {log.error("exception", e);
                }
                countDownLatch.countDown();});
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {lock.lock();
        try {count++;} finally {lock.unlock();
        }
    }
}

我们首先使用 private final static Lock lock = new ReentrantLock() 声明一个所得实例,然后使用

lock.lock();
        try {count++;} finally {lock.unlock();
        }

进行加锁和解锁操作。

我们在通过一个例子来看看这个 ReentrantReadWriteLock 怎么用。

@Slf4j
public class LockExample3 {private final Map<String, Data> map = new TreeMap<>();

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private final Lock readLock = lock.readLock();

    private final Lock writeLock = lock.writeLock();

    public Data get(String key) {readLock.lock();
        try {return map.get(key);
        } finally {readLock.unlock();
        }
    }

    public Set<String> getAllKeys() {readLock.lock();
        try {return map.keySet();
        } finally {readLock.unlock();
        }
    }

    public Data put(String key, Data value) {writeLock.lock();
        try {return map.put(key, value);
        } finally {readLock.unlock();
        }
    }

    class Data {}}

通过 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock() 声明一个 ReentrantReadWriteLock,然后再分别获取 private final Lock readLock = lock.readLock() private final Lock writeLock = lock.writeLock() 读锁和写锁。
我们在这个 map 读的时候加上读锁在写的时候加上写锁,但是这里有问题就是这个锁是悲观锁,也就是说在执行写锁的时候一定不能有读锁,当读操作特 特别多的时候很有可能会让写锁一直无法执行。

我们看一下官方的例子学习一下,StampedLock

import java.util.concurrent.locks.StampedLock;

public class LockExample4 {

    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); // 释放读锁或写锁
            }
        }
    }
}

我们再将前面的里改成 StampedLock

@Slf4j
public class LockExample5 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static int count = 0;

    private final static StampedLock lock = new StampedLock();

    public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {executorService.execute(() -> {
                try {semaphore.acquire();
                    add();
                    semaphore.release();} catch (Exception e) {log.error("exception", e);
                }
                countDownLatch.countDown();});
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {long stamp = lock.writeLock();
        try {count++;} finally {lock.unlock(stamp);
        }
    }
}

这里和之前的不一样的地方就是

   long stamp = lock.writeLock();
        try {count++;} finally {lock.unlock(stamp);
        }

在加锁后会返回一个值,解锁的时候需要传入这个值。

正文完
 0