15 | synchronized 和 ReentrantLock 有什么区别呢?

↑↑↑ 欢迎 点赞、关注、收藏!!!,10 年 IT 行业老鸟,持续分享更多 IT 干货

注: 本笔记为 公司内部技术小组持续学习 2 年多时间 + 个人整理不下 5 次的结果产出。

目录

15 | synchronized 和 ReentrantLock 有什么区别呢?

问题

回答

分析

扩展

什么是线程安全

ReentrantLock

条件变量(java.util.concurrent.Condition)

小结


15 | synchronized 和 ReentrantLock 有什么区别呢?


问题

  1. synchronized 和 ReentrantLock 有什么区别?

  2. 有人说 synchronized 最慢,这话靠谱吗?


回答

  1. synchronized 是 Java 内建的同步机制,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或阻塞在那里。

  2. ReentrantLock(再入锁),提供更多细节控制,代码书写也更加灵活。

    必须要明确调用 unlock() 方法释放,不然就会一直持有该锁。

  3. 早期版本 synchronized 在很多场景下性能相差较大;后续版本进行了较多改进,在低竞争场景中表现可能优于 ReentrantLock。


分析

  1. 基础知识

    • 什么是线程安全。

    • synchronized、ReentrantLock 的使用与案例。

  2. 更近一步

    • 掌握 synchronized、ReentrantLock 底层实现;

    • 理解锁膨胀、降级;理解偏斜锁、自旋锁、轻量级锁、重量级锁等概念。

    • java.util.concurrent.lock 各种不同实现和案例分析。


扩展

什么是线程安全
  1. 定义

    • 线程安全是一个多线程环境下的正确性的概念,也就是保证多线程环境下共享的可修改的状态的正确性

  2. 保证线程安全的两个方法

    • 封装:把对象内部状态隐藏、保护起来。

    • 不可变:final 和 immutable。

  3. 线程安全需要保证几个基本特性

    • 原子性,操作中途不被其他线程干扰。

    • 可见性,一个线程修改的修改,能够立即被其他线程知晓。volatile 负责保证可见性。

    • 有序性,保证线程内串行语义,避免指令重排等。

  4. 示例:分析原子性需求体现在哪里。

    • 代码

    •  public class ThreadSafeSample {
           public int sharedState;
           public void nonSafeAction() {
               while (sharedState < 1000000) {
                   int former = sharedState++;
                   int latter = sharedState;
                   if (former != latter - 1) {
                       System.out.println(
                               "Observer data race, former is " + former + ", latter is " + latter
                       );
                   }
               }
           }
       ​
           public static void main(String[] args) throws InterruptedException {
               ThreadSafeSample sample = new ThreadSafeSample();
               Thread threadA = new Thread() {
                   @Override
                   public void run() {
                       sample.nonSafeAction();
                   }
               };
               Thread threadB = new Thread() {
                   @Override
                   public void run() {
                       sample.nonSafeAction();
                   }
               };
       ​
               threadA.start();
               threadB.start();
               threadA.join();
               threadB.join();
           }
       }
    • 输出

    •  Observer data race, former is 11423, latter is 11433
       Observer data race, former is 63472, latter is 63486
       Observer data race, former is 67814, latter is 67821
  5. 使用 synchronized 优化

    •        while (sharedState < 1000000) {
                 int former;
                 int latter;
                 synchronized (this) {
                     former = sharedState++;
                     latter = sharedState;
                 }
         
                 if (former != latter - 1) {
                     System.out.println(
                             "Observer data race, former is " + former + ", latter is " + latter
                     );
                 }
             }
ReentrantLock
  1. 什么是再入锁?

    • 一个线程可以再次获取它已经获取到的锁。

  2. 设置公平性(fairness)

    •    
       ReentrantLock fairLock = new ReentrantLock(true);
    • synchronized,无法进行公平的选择。

    • 建议:只有当程序确实有公平性的需要的时候,才有必要指定它。

  3. 每一个 lock() 建议都立即对应一个 try-catch-finally

    •    
       ReentrantLock fairLock = new ReentrantLock(true);// 这里是演示创建公平锁,一般情况不需要。
       fairLock.lock();
       try {
         // do something
       } finally {
          fairLock.unlock();
       }
条件变量(java.util.concurrent.Condition)
  1. Condition 是将 wait、notify、notifyAll 等操作转化为相应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。

  2. 场景:ArrayBlockingQueue

    •  public ArrayBlockingQueue(int capacity, boolean fair) {
         if (capacity <= 0)
             throw new IllegalArgumentException();
         this.items = new Object[capacity];
         lock = new ReentrantLock(fair);
         notEmpty = lock.newCondition();
         notFull =  lock.newCondition();
       }
       ​
       ​
       public E take() throws InterruptedException {
         final ReentrantLock lock = this.lock;
         lock.lockInterruptibly();
         try {
             while (count == 0)
                 notEmpty.await();
             return dequeue();
         } finally {
             lock.unlock();
         }
       }
       ​
       ​
       private void enqueue(E e) {
         // assert lock.isHeldByCurrentThread();
         // assert lock.getHoldCount() == 1;
         // assert items[putIndex] == null;
         final Object[] items = this.items;
         items[putIndex] = e;
         if (++putIndex == items.length) putIndex = 0;
         count++;
         notEmpty.signal(); // 通知等待的线程,非空条件已经满足
       }
       ​
小结
  1. 大多数情况下,无需纠结于性能,还是考虑代码书写结构的便利性、可维护性等

参考地址:Java核心技术面试精讲_Java面试_Java基础-极客时间

↑↑↑ 欢迎 点赞、关注、收藏!!!,10 年 IT 行业老鸟,持续分享更多 IT 干货

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写文章的大米

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值