5

大厂面试题:ReentrantLock 与 synchronized异同点对比 - JavaBuild

 4 months ago
source link: https://www.cnblogs.com/JavaBuild/p/18149963
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在过去的博文中我们学习了ReentrantLocksynchronized这两种Java并发使用频率最高的同步锁,在很多大厂面试题中有个经典考题:

ReentrantLock 与 synchronized异同点对比!

今天我们针对这一考题来做一个尽可能全面的总结哈。

ReentrantLock 与 synchronized

ReentrantLock是一种独占式的可重入锁,位于java.util.concurrent.locks中,是Lock接口的默认实现类,底部的同步特性基于AQS实现,和synchronized关键字类似,但更灵活、功能更强大、也是目前实战中使用频率非常高的同步类。

synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API

synchronized 是依赖于 JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。

ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),ReentrantLock 比 synchronized 增加了一些高级功能。

  1. ReentrantLock 是一个类,而 synchronized 是 Java 中的关键字;
  2. ReentrantLock 必须手动释放锁。通常需要在 finally 块中调用 unlock 方法以确保锁被正确释放;
  3. ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁;
  4. synchronized 会自动释放锁,当同步块执行完毕时,由 JVM 自动释放,不需要手动操作;
  5. ReentrantLock 可以实现多路选择通知(可以绑定多个 Condition),而 synchronized 只能通过 wait 和 notify/notifyAll 方法唤醒一个线程或者唤醒全部线程(单路通知);
  6. ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。而synchronized不具备这种特点。
  7. ReentrantLock: 通常提供更好的性能,特别是在高竞争环境下;
  8. synchronized: 在某些情况下,性能可能稍差一些,但随着 JDK 版本的升级,性能差距已经不大了。

【注】:Condition是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活,我们在后面的学习中会耽误聊一聊它!

虽然说JDK1.6后synchronized的性能有很大的提升了,但是相比较而言,两者之间仍然存在性能差别,我们通过一个小demo来测试一下。

public class Test { private static final int NUM_THREADS = 10; private static final int NUM_INCREMENTS = 1000000; private int count1 = 0; private int count2 = 0; private final ReentrantLock lock = new ReentrantLock(); private final Object syncLock = new Object(); public void increment1() { lock.lock(); try { count1++; } finally { lock.unlock(); } } public void increment2() { synchronized (syncLock) { count2++; } } public static void main(String[] args) throws InterruptedException { Test test = new Test(); // ReentrantLock性能测试 long startTime = System.nanoTime(); Thread[] threads = new Thread[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < NUM_INCREMENTS; j++) { test.increment1(); } }); threads[i].start(); } for (Thread thread : threads) { thread.join(); } long endTime = System.nanoTime(); System.out.println("ReentrantLock完成时间: " + (endTime - startTime) + " ns"); // synchronized性能测试 startTime = System.nanoTime(); for (int i = 0; i < NUM_THREADS; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < NUM_INCREMENTS; j++) { test.increment2(); } }); threads[i].start(); } for (Thread thread : threads) { thread.join(); } endTime = System.nanoTime(); System.out.println("synchronized完成时间: " + (endTime - startTime) + " ns"); }}

我们采用10个线程,每个线程做加1000000操作,执行时间对比如下:

//1000000万数据量时ReentrantLock完成时间: 272427700 nssynchronized完成时间: 675759100 ns
//10000数据量时ReentrantLock完成时间: 52207600 nssynchronized完成时间: 11291600 ns

很明显在数据量比较大的时候,竞争激烈时,ReentrantLock的性能要比synchronized好很多,但在数据量较低的情况下,会呈现出不同的结果。

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

image


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK