synchronized和ReentrantLock有什么区别呢?

在并发阶段,从前面几篇专栏中我们也提到过了对于并发的一些了解,我们也知道了一些集合框架都是线程不安全的,像HashTable这种安全的,或者Collections里面包装的几个API,是能保证线程安全的,但是就像我们之前介绍的,里面的一些像get put remove的一些方法,加了各种各样的锁,来保证线程安全,这样在我们进行低并发场景的时候或许没有什么大问题。
但是,如果在高并发场景就会出现很多错误,虽说SpringCloud里面有熔断器之类拮据这方面的工具框架,但是,我们更应该做到的是,以更高的效率来完成并发,所以,接下来将会从并发的角度,来讲述一下,何为并发。

首先从我们熟知的synchronized和ReentrantLock入手,来讲讲这二者有什么区别?

简述

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

ReentrantLock,通常翻译为再入锁,它的语义和synchronized基本是相同的。再入锁,通过.lock()实现,代码也书写的更加灵活。于此同时,ReentrantLock提供了很多实用的方法,也就是粒度更细的synchronize,可以这么理解,能实现一些synchronize无法实现的细节功能,但是要用.unlock()释放,不然会一直上锁。

从性能来看,二者无法一概而论,早期synchronize性能略差,但是经过这几个版本的不断优化,现在也比ReentrantLock要好了。

从面试角度将,面试官经常会问:

  1. 什么是线程安全
  2. synchronized、ReentrantLock等机制的基本使用与案例
  3. synchronize与ReentrantLock的底层实现
  4. 理解锁膨胀,锁降级,偏斜锁,自旋锁,轻量级锁,重量级锁等概念
  5. 掌握并发包的使用,你懂的

这是专门讲并发的第一篇专栏,所以先说的简单一些,后面会对一些基础概念进行补充。

扩展

首先,什么是线程安全?
线程安全的前提是,状态共享, 线程安全有两个方法,分别是

  1. 封装,通过封装,来让对象内部状态隐藏,保护起来
  2. 不可变,这个我在第一篇专栏中提过,道理是一样的。

线程安全必须要掌握几个特性:

  1. 原子性:简单来说就是相关操作不会中途被其他线程干扰,一般通过锁同步机制来实现。

  2. 可见性:由于状态共享,所以线程中一个变量改变,其他线程要立刻知晓,并且反应到主存,volatile就是负责保证可见性的。
    volatile:为了保证线程的高效,我们会将变量(假设是A)复制为另一个变量(假设为B),访问A麻烦,所以会代替访问而去访问B,这就会让AB不同步,volatile就是防止这种情况的。

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

这些或许很难理解,所以看代码吧,首先来理解一下原子性。

synchronized和ReentrantLock有什么区别呢?_第1张图片
我们可以看出,仅仅两个线程,就会出现,former和latter不相等的情况

运行结果为
synchronized和ReentrantLock有什么区别呢?_第2张图片

然后我们用synchronized包起来,用this做互斥单元,再次运行。

就可以避免别的线程修改shareState。

后面会对synchronized和其他锁实现的更多细节进行更加深入的分析。代码中使用synchronized十分便利。

然后再看看ReentrantLock,可能很多人都不知道这个词,什么叫做再入,它的意思是一个线程试图获取一个它已经获取的锁的时候,这个获取动作就自动成功。这是对锁获取粒度的一个概念,也就是锁的持有是以线程为单位而不是以调用次数为单位,再入锁可以设置公平性(fairness),保证选择的时候是公平的。

ReentrantLock fairLock = new ReentrantLock(true);

如果使用synchronized,我们则无法选择公平性,也就是操作系统线程调度的选择,但是有些时候,公平性或许没有那么重要,java默认的调度很少会出现饥饿现象。与此同时,保证公平的前提会引入额外开销,自然会导致额外的吞吐量下降。所以,看场景选择这两种锁。

除此之外,ReentrantLock还有一些其他精细的功能是synchronized很难实现的,如:

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

从性能角度,synchronized早期的实现比较低效,对比ReentrantLock,大多数场景性能都相差较大。但是在Java 6中对其进行了非常多的改进,可以参考性能对比,在高竞争情 况下,ReentrantLock仍然有一定优势。我在下一讲进行详细分析,会更有助于理解性能差异产生的内在原因。在大多数情况下,无需纠结于性能,还是考虑代码书写结构的便利 性、可维护性等。

你可能感兴趣的:(java并发,Java并发编程)