Java线程安全与各种锁

为了便于自己阅读理解,本文整理自《深入理解Java虚拟机》第3版。

同步

同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一条线程使用。

同步的手段有两种,一种是互斥同步,另一种是非阻塞同步。

1. 互斥同步

互斥是实现同步的一种手段,临界区、互斥量、信号量都是常见的互斥实现方式。

互斥同步是一种悲观的并发策略,它总是认为只要不去做正确的同步措施(如加锁),就肯定会出现问题。

乐观锁

互斥同步实现的锁,就是悲观锁。

Synchronized

在Java里面,最基本的互斥同步手段就是synchronized关键字。synchronized关键字经过Javac编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。这两个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象。

在执行monitorenter指令时,首先要去尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经持有了那个对象的锁,就把锁的计数器的值加一,而在执行monitorexit指令时会将锁计数器的值减一。一旦计数器的值为零,锁随即就被释放了。如果获取对象锁失败,那当前线程就应当被阻塞等待,直到请求锁定的对象被持有它的线程释放为止。

可重入锁

可重入是指一条线程能够反复进入被它自己持有锁的同步块的特性,即锁关联的计数器,如果持有锁的线程再次获得它,则将计数器的值加一,每次释放锁时计数器的值减一,当计数器的值为零时,才能真正释放锁。

  • 显然,被synchronized修饰的同步块是可重入的。
  • 重入锁(ReentrantLock)是Lock接口最常见的一种实现。

公平锁

公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁;而非公平锁则不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁。

  • synchronized中的锁是非公平的。
  • ReentrantLock在默认情况下也是非公平的,可以通过构造函数设置为公平锁,不过会导致性能急剧下降。

用ReentrantLock还是synchronized

ReentrantLock在功能上是synchronized的超集,性能上也不会弱于synchronized,那是否应该直接抛弃synchronized呢?当然不是,基于以下理由,推荐优先使用synchronized:

  1. synchronized是在Java语法层面的同步,足够清晰简单,每个程序员都熟悉,但Lock接口则并非如此。
  2. Lock需要人为的在finally块中释放锁,而synchronized则是由Java虚拟机来确保锁的释放。
  3. 从长远看,Java虚拟机更容易针对synchronized来进行优化。

2. 非阻塞同步

非阻塞同步是基于冲突检测的乐观并发策略,通俗的说就是不管风险,先进行操作,如果没有其他线程争用共享数据,那操作就直接成功了;如果共享数据被争用,再进行其他的补偿措施,最常用的补偿措施就是不断的重试。

乐观锁

非阻塞同步实现的锁,就是乐观锁。

CAS操作

CAS(Compare-and-Swap),比较并交换。CAS包含了三个操作数,分别是需要读写的内存位置V、旧的预期值A和准备设置的新值B。当且仅当V的值等于A时,处理器才会通过原子方式用新值B来更新V的值,否则不会执行任何操作。无论是否更新了V值,都会返回V原有的值。

猜你喜欢

转载自blog.csdn.net/u013534071/article/details/109082880
今日推荐