前言
Java中的锁主要分为两种,一种是synchronized修饰的锁,另一种是J.U.C提供的锁,其最核心的锁就是ReentrantLock
ReentrantLock和synchronized区别
- 可重入性:两者都是同一个线程进入一次,进入之后锁的计数器就会自增+1,需要等到锁的计数器为0时才会释放锁
- 实现逻辑:synchronized是基于JVM,ReentrantLock是基于JDK
- 性能:本来synchronized性能比ReentrantLock差很多,但是后面synchronized加入了偏向锁之后,两者性能也就差不多了
- 便利性:synchronized使用起来比较方便,一个方法即可,并且是由编译器保证加锁和释放锁的,ReentrantLock则需要手工声明和释放锁,而且建议是最好放在finally中进行锁的释放操作
- 锁的灵活度和细粒度:ReentrantLock比synchronized要好一点
- 公平锁和非公平锁切换:ReentrantLock可指定锁是公平锁还是非公平锁,而synchronized只能是非公平锁(公平锁就是先等待的线程先获得锁)
- 唤醒机制:ReentrantLock提供了一个Condition类,可以分组唤醒需要唤醒的线程,而synchronized只能随机唤醒一个或全部线程
- 中断线程机制: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 版权协议,转载请附上原文出处链接和本声明。