【锁】ReentrantLock

  • Post author:
  • Post category:其他


前言

Java中的锁主要分为两种,一种是synchronized修饰的锁,另一种是J.U.C提供的锁,其最核心的锁就是ReentrantLock

ReentrantLock和synchronized区别

  1. 可重入性:两者都是同一个线程进入一次,进入之后锁的计数器就会自增+1,需要等到锁的计数器为0时才会释放锁
  2. 实现逻辑:synchronized是基于JVM,ReentrantLock是基于JDK
  3. 性能:本来synchronized性能比ReentrantLock差很多,但是后面synchronized加入了偏向锁之后,两者性能也就差不多了
  4. 便利性:synchronized使用起来比较方便,一个方法即可,并且是由编译器保证加锁和释放锁的,ReentrantLock则需要手工声明和释放锁,而且建议是最好放在finally中进行锁的释放操作
  5. 锁的灵活度和细粒度:ReentrantLock比synchronized要好一点
  6. 公平锁和非公平锁切换:ReentrantLock可指定锁是公平锁还是非公平锁,而synchronized只能是非公平锁(公平锁就是先等待的线程先获得锁)
  7. 唤醒机制:ReentrantLock提供了一个Condition类,可以分组唤醒需要唤醒的线程,而synchronized只能随机唤醒一个或全部线程
  8. 中断线程机制:ReentrantLock提供了lock.lockInterruptibly(),它通过循环调用CAS操作来实现加锁,性能相对来说比较好,因为避免了线程进入内核态的阻塞

synchronized自身优点

  • 不需要手动释放锁,全部由JVM自动处理,而且如果遇到异常,JVM也会自动释放锁
  • 管理锁定请求和释放时,JVM在生成线程转储时能够锁定信息,这个适用于调试
  • synchronized可以在所有JVM版本中工作

代码DEMO

@Slf4j
public class ReentrantLockDemo {
    //请求总数
    public static int clientTotal = 5000;
    //同时并发执行的线程数
    public static int threadTotal = 200;
    public static int count = 0;
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        //使用工厂类Executors快速创建线程池
        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);
                }
                //线程完成之后计数器都会-1
                countDownLatch.countDown();
            });
        }
        //阻塞,直到计数器为0的时候,释放锁
        countDownLatch.await();
        //关闭线程服务
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        lock.lock();
        try {
            //代码逻辑
            count++;
            log.info("thread count:{}", count);
        } finally {
            lock.unlock();
        }
    }
}

进阶版代码DEMO-StampedLock

介绍

StampedLock的状态由版本和模式两个部分组成,锁获取方法返回的是一个数字作为票据,用相应的锁状态来表示并控制相关的访 问,数字0表示没有写锁被授权访问。

在读线程越来越多的场景下,StampedLock大幅度提升了程序的吞吐量。

代码

@Slf4j
public class StampedLockDemo {
    //请求总数
    public static int clientTotal = 5000;
    //同时并发执行的线程数
    public static int threadTotal = 200;
    public static int count = 0;
    private static final StampedLock lock = new StampedLock();

    public static void main(String[] args) throws InterruptedException {
        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类型的票据
        long stamp = lock.writeLock();
        try {
            count++;
        } finally {
            //释放锁的时候带上加锁时返回的票据
            lock.unlock(stamp);
        }
    }
}

如何选择

  • 当竞争者在少数的时候, synchronized优选

  • 当竞争者不在少数同时竞争者的增长趋势可预估的情况下,用ReentrantLock较好

  • synchronized不会造成死锁现象,其他的如果使用不当都会引发死锁



版权声明:本文为qq_38377525原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。