并发与多线程【四】——同步与互斥

引言

这部分内容详解线程的同步与互斥,解决线程同步与互斥的主要方式是 CASsynchronizedlock

CAS 与 ABA 问题

什么是 CAS ?

CAS 是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现都是基于 CAS 的,用于解决线程的同步与互斥。解决线程同步与互斥的主要方式除了 CAS 外,还有另外两种:synchronized 和 lock

CAS 的操作流程:当线程在读取数据时不进行加锁,在准备回写数据时,比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。这是一种乐观策略,认为并发操作并不总会发生。比较并写回的操作是通过操作系统原语实现的,保证执行过程中不会被中断。

但是,通过上述操作流程可以发现,CAS 容易引发 ABA 问题。

什么是 ABA 问题?

ABA 问题是指如果线程 T1 读取 A 值以后,发生两次写入,先由线程 T2 写回了 B,又由线程 T3 写回了 A,此时 T1 在写回比较时,值还是 A,就无法判断是否发生过修改。

ABA 问题不一定会影响结果,但是需要防范,可以通过增加额外的标志位或时间戳来解决。而 JUC 工具包中就提供了这样的类。

synchronized

synchronized 相信大家都不陌生,个别公司面试还有让面试者特意写一下这个单词的。。大概是太长而且过分依赖开发工具,相信有部分猿友能写出来但还是不太敢确信。

synchronized 是最常用的线程同步手段之一,那它是怎样保证同一时刻只有一个线程可以进入临界区的呢?

synchronized 对对象进行加锁,在 JVM 中,对象在内存中分为三块区域:对象头、实例数据和对齐填充。在对象头中保存了锁标志位和指向 monitor 对象的起始地址。如图所示,右侧就是对象对应的 monitor 对象。当 monitor 被某个线程持有后,就会处于锁定状态,如图中 owner 部分,会指向持有 monitor 对象的线程。另外 monitor 中还有两个队列,用来存放进入和等待获取锁的线程。

当 synchronized 应用在方法上时,在字节码中是通过方法的 ACC_SYNCHRONIZED 标志来实现的。

当 synchronized 应用在同步块上时,在字节码中是通过 monitorenter 和 monitorexit 来实现的。

针对 synchronized 获取锁的方式,JVM 使用了升级锁的优化方式。就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量锁,如果失败,就会短暂自旋,防止线程被挂起。最后如果以上都失败,就升级为重量锁。

AQS 与 Lock

在介绍 Lock 之前,先介绍 AQS,也就是队列同步器,这是实现 Lock 的基础。下图是 AQS 的结构图:

 

 

从图中可以看出 AQS 有一个 state 标志位,值为1时表示有线程占用,其他线程需要进入同步队列等待。同步队列是一个双向链表。

当获得锁的线程需要等待某个条件时,会进入 condition 的等待队列,等待队列可以有多个。当 condition 条件满足时,线程会从等待队列重新进入同步队列进行获取锁的竞争。ReentrantLock(重入锁) 就是基于 AQS 实现的,如下图所示,ReentrantLock 内部有公平锁和非公平锁两种实现,差别就在于新来的线程是否比已经在同步队列中的等待线程更早获得锁。

 

 和 ReentrantLock 类似,Semaphore 也是基于 AQS 的,差别在于 ReentrantLock 是独占锁,Semaphore 是共享锁。

 

posted @ 2020-02-18 12:10  习惯沉淀  阅读(538)  评论(0编辑  收藏  举报