synchronized 和 ReentrantLock 有什么区别?

简介: synchronized 和 ReentrantLock 有什么区别?

synchronized 和 ReentrantLock 有什么区别?


synchronized 最慢,这话靠谱么?

Synchronized  是 Java  内建的同步机制,所以也有人称其为 Intrinsic Locking,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞。Java 5 之前,synchronized 是仅有的同步手段,在代码中,Synchronized 可以用来修饰方法,代码块。

ReentrantLock , 通常翻译为可重入锁,是 Java 5 提供的锁实现,通过代码直接调用 lock() 方法获取代码书写也更加灵活,与此同时,ReentrantLock 提供了很多实用的方法,可以实现很多 synchronized 无法做到的细节控制,但是需要明确调用 unlock()方法释放。


什么是线程安全?


《Java并发编程实战》中定义,线程安全是一个多线程环境下正确性的概念。保证多线程环境下共享的可修改的状态的正确性。这里的状态其实可以看做程序中的数据。

换个角度,如果状态是不共享的, 不可修改的,也就不存在线程安全问题了。


如何保证线程安全


  • 封装: 通过封装,将内部对象隐藏保护起来。
  • 不可变:fina变量产生了某种程度的不可变( immutable)的效果,所以,可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值final变量,有利于减少额外的同步开肖,也可以省去一些防御性拷贝的必要


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


  • 原子性,相关操作不会中途被其他线程干扰,一般通过同步机制实现。
  • 可见性,一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主內存上,v. latile就是负责保证可见性的
  • 有序性, 保证线程内串性语义,避免指令重排。


一个非线程安全的例子


public class ThreadSafeSample {
    public int shareState;
    public void noSafeAction() {
        while (shareState < 10000) {
            int former = shareState++;
            int latter = shareState;
            if (former != latter - 1) {
                System.out.println("Observerd 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.noSafeAction();
            }
        };
        Thread threadB = new Thread() {
            public void run() {
                sample.noSafeAction();
            };
        };
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
    }
}

运行结果:


Observerd data race, former is 8832, latter is 8836

可以看到 保证 shareState 这个字段是线程不安全的,两个线程同时操作,会导致与预期结果不符合。如果想要线程安全,可以做如下修改:


synchronized(this){
    int former = shareState++;
    int latter = shareState;
    if (former != latter - 1) {
        System.out.println("Observerd data race, former is " + former + ", latter is " + latter);
    }
}

synchronized ,ReentrantLock 底层实现。


synchronized


如果用 Javap反编译,可以看到类似片段,利用 monitorenter/monitorexit通对实现了同步的语义。测试代码:

public class SynchronizedDemo {
    public  static synchronized void doSth(){
        System.out.println("Hello World");
    }
    public static void doSth1(){
         synchronized(SynchronizedDemo.class){
             System.out.println("Hello World 1");
         }
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        SynchronizedDemo.doSth1();
    }
}

javac javap 可以看到 synchronized 内部实现


640.png


ReentrantLock


  • 可重入性 它是表示当一个线程试图获取一个它已经获取的锁时,这个获取动作就自动成功,这是对锁获取粒度的一个概念,也就是锁的持有是以线程为单位而不是基于调用次数。Java锁实现强调再入性是为了和 pthread的行为进行区分。
  • 公平性 再入锁可以设置公平性( fairness),我们可在创建再入锁时选择是否是公平的。


ReentrantLock fairLock= new ReentrantLock(true)

这里所谓的公平性是指在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。公平性是减少线程¨饥饿”(个别线程长期等待锁,但始终无法获取)情况发生的一个办法,避免线程饿死。使用示例:为保证锁释放,每一个lock动作,我建议都立即对应一个try- catch- finally,典型的代码结构如下,这是个良好的习惯。


Reentrantlock fairlock= new Reentrantlock(true); //这里是演示创健公平锁,一般情况不需要。
try{
  // do something
}finally{
  fairLock.unlock();
}

Reentrantlock 与 synchronized区别


  • 带超时的获取锁尝试
  • 可以判断是否有线程,或者某个特定线程,在排队等待获取锁。
  • 可以响应中断请求


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



java.util.concurrent.Condition。


这里我特别想强调条件变量(java.util.concurrent.Condition),如果说 Reentrantlock是 synchronized的替代选择, Condition则是将wait、 notify、 notify等操作转化为相应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。

Condition 的一个应用场景是标准类库中的 ArrayBlockingQueue。 ArrayBlockingQueue 构造函数


/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
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();
}

两个Condition 都是从一个可重入锁中创建出来的。


public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

当队列为空时,试图take的线程的正确行为应该是等待入队发生,而不是直接返回,这是 Blockingqueue的语义,使用条件notempty就可以优雅地实现这一逻辑。那么,怎么保证入队触发后续take操作呢?请看 enqueue实现


private void enqueue(E x) {
      // assert lock.getHoldCount() == 1;
      // assert items[putIndex] == null;
      final Object[] items = this.items;
      items[putIndex] = x;
      if (++putIndex == items.length)
          putIndex = 0;
      count++;
      notEmpty.signal();
  }

通过 signal/await的组合,完成了条件判断和通知等待线程,非常顺畅就完成了状态流转。注意, signa和 await成对调用非常重要,不然假设只有 await动作,线程会一直等待直到被打断(interrupt)。


总结


  1. 用法比较

Lock使用起来比较灵活,但是必须有释放锁的配合动作Lock必须手动获取与释放锁,而 synchronized不需要手动释放和开启锁Lock只适用于代码块锁,而 synchronized可用于修饰方法、代码块等

  1. 特性比较

ReentrantLock的优势体现在具备尝试非阻塞地获取锁的特性:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁迮被中断地获取锁的特性:与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛岀,同时锁会被释放超时获取锁的特性:在指定的时间范围内获取锁;如果截止时间到了仍然 无法获取锁,则返回.

  1. 注意事项

在使用 Reentrantlock类的时,一定要注意三点在 finally 中释放锁,目的是保证在获取锁之后,最终能够被释放不要将获取锁的过程写在try块內,因为如果在获取锁时发生了异常,异常拋岀的同时,也会导致锁无故被释放。 Reentrantlock提供了—个 newCondition的方法,以便用户在同一锁的情况下可以根据不同的情况执行等待或唤醒的动作。

AQS和 Condition各自维护了不同的队列,在使用lock和 condition的时候,其实就是两个相移动。

相关文章
|
设计模式 算法 前端开发
【面试题】什么是策略模式?用了策略模式之后,再也不用写那么多 if else 了,真香!
【面试题】什么是策略模式?用了策略模式之后,再也不用写那么多 if else 了,真香!
214 0
|
传感器 监控 搜索推荐
智能服装:集成健康监测功能的纺织品——未来穿戴科技的新篇章
【10月更文挑战第7天】智能服装作为穿戴科技的重要分支,正以其独特的技术优势和广泛的应用前景,成为未来科技发展的亮点之一。它不仅改变了我们对服装的传统认知,更将健康监测、运动训练、医疗康复等功能融为一体,为我们的生活带来了更多的便利和可能。随着技术的不断进步和市场的日益成熟,我们有理由相信,智能服装将成为未来穿戴科技的新篇章,引领我们走向更加健康、智能、可持续的生活方式。
|
SQL 关系型数据库 数据库
OceanBase数据库常见问题之密码输入错误次数多被锁了如何解决
OceanBase 是一款由阿里巴巴集团研发的企业级分布式关系型数据库,它具有高可用、高性能、可水平扩展等特点。以下是OceanBase 数据库使用过程中可能遇到的一些常见问题及其解答的汇总,以帮助用户更好地理解和使用这款数据库产品。
|
搜索推荐
基于若依ruoyi-nbcio增加flowable流程待办消息的提醒,并提供右上角的红字数字提醒(四)
基于若依ruoyi-nbcio增加flowable流程待办消息的提醒,并提供右上角的红字数字提醒(四)
502 0
|
机器学习/深度学习 人工智能 自然语言处理
当前AI大模型在软件开发中的创新应用与挑战
【10月更文挑战第31天】2024年,AI大模型在软件开发领域的应用取得了显著进展,从自动化代码生成、智能代码审查到智能化测试,极大地提升了开发效率和代码质量。然而,技术挑战、伦理与安全问题以及模型可解释性仍是亟待解决的关键问题。开发者需不断学习和适应,以充分利用AI的优势。
|
关系型数据库 C# 数据库
.NET 8.0 开源在线考试系统(支持移动端)
【10月更文挑战第27天】以下是适用于 .NET 8.0 的开源在线考试系统(支持移动端)的简介: 1. **基于 .NET Core**:跨平台,支持多种数据库,前后端分离,适用于多操作系统。 2. **结合 Blazor**:使用 C# 开发 Web 应用,支持响应式设计,优化移动端体验。 3. **基于 .NET MAUI**:跨平台移动应用开发,一套代码多平台运行,提高开发效率。 开发时需关注界面设计、安全性与稳定性。
343 4
|
运维 监控 Kubernetes
高效应对突增流量:构建弹性高性能的SMS网关策略
本篇内容来自于ArchSummit全球架构师峰会演讲实录。
490 1
|
数据采集 存储 算法
基于BP算法的SAR成像matlab仿真
**摘要:** 基于BP算法的SAR成像研究,利用MATLAB2022a进行仿真。SAR系统借助相对运动合成大孔径,提供高分辨率图像。BP算法执行回波数据预处理、像素投影及图像重建,实现精确成像。优点是高精度和强适应性,缺点是计算量大、内存需求高。代码示例展示了回波生成、数据处理到插值显示的全过程。
|
Kubernetes Linux KVM
在Linux中,有哪些虚拟化技术?
在Linux中,有哪些虚拟化技术?

热门文章

最新文章