多线程进阶(常见的锁策略、CAS以及Synchronized原理)

文章目录

  • 一、常见的锁策略
    • 1.乐观锁 VS 悲观锁
    • 2.自旋锁(Spin Lock)
    • 3.重量级锁 VS 轻量级锁
    • 4.可重入锁
    • 5.独占锁
    • 6.非公平锁 VS 非公平锁
    • 7.读写锁
  • 二、CAS
    • 1.什么是CAS
    • 2.CAS原理
    • 3.CAS的应用
    • 4.CAS的ABA问题
  • 三、Synchronized原理
    • 1.加锁工作过程
    • 2.其他的优化操作


一、常见的锁策略

1.乐观锁 VS 悲观锁

悲观锁:
大多数时间看,存在线程冲突(悲观地看待问题),每次都先加锁,再释放锁
乐观锁:
大多数时间看,没有线程冲突的(乐观地看待问题),每次都不加锁(Java层面看),每次都直接执行修改数据的操作,返回修改是否成功的结果,程序自行处理逻辑

冲突:同一个时间,多个线程并发并行的执行某个代码
不加锁:从cpu层面看,是加锁的
例子:
一个房间,上课时间比较长,容易出现冲突,使用悲观锁
一个房间,搬东西,速度很快,不容易冲突,使用乐观锁

Synchronized初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略

乐观锁的一个重要功能就是要检测出数据是否发生访问冲突,我们可以引入一个”版本号“来解决
多线程进阶(常见的锁策略、CAS以及Synchronized原理)_第1张图片
Java层面:无锁,直接修改某个共享变量

boolean result = update(store,100,1)//直接修改,不加锁

返回true:主存store修改成功——线程1
返回false:主存store不做任何操作——线程2

2.自旋锁(Spin Lock)

这里名词虽然也包含锁,但实际也是Java层面无锁的操作

while(true){
	boolean result = 乐观锁的方式修改数据;
	if(result){
		break;
	}
}

自旋的方式,执行乐观锁修改数据的操作

绝大多数时间,没有线程冲突不代表完全没有冲突
也提供一种解决冲突的方案:反复尝试修改数据(自旋名称的由来)

一般来说,乐观锁都考虑结合自旋锁来操作
如果满足乐观锁的使用条件:冲突概率比较小,且即使发生冲突,也能够很快的得到执行,就可以使用。

3.重量级锁 VS 轻量级锁

悲观锁: 加锁,执行,释放锁
因为代价比较大,所以也叫重量级锁

乐观锁: Java层面无锁,cpu层面加锁
Java语言层面看代价小,也叫轻量级锁

乐观锁属于一种解决冲突的设计思想:尝试修改,直接返回修改结果
实现上:Java中是CAS(修改变量),程序+数据库也可以使用乐观锁来解决冲突
如两个用户同时修改页面上的学生信息这条数据(就是客户端并发冲突):这里数据库类似之前操作主存,先读到页面,再修改,写回数据库
解决方法:数据库表设计一个version字段,修改的时候判断一下

CAS:Java提供的对变量修改操作的乐观锁实现

4.可重入锁

同一个线程可以重复申请到同一个对象的锁

5.独占锁

同一个时间,只有一个线程申请到锁

6.非公平锁 VS 非公平锁

线程获取到锁不是以申请锁的时间先后顺序来获取到锁

排队买票就是公平锁(按时间顺序买到票)
不排队买票,大家划拳买票,就是非公平锁

非公平锁的缺陷: 可能出现线程饥饿的现象
优点: 效率更高

7.读写锁

ReentrantReadWriteLock(适用于读多写少的场景)

ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReadLock readLock = readWriteLock.readLock();//读锁
WriteLock writeLock = readWriteLock.writeLock();//写锁

在某些场景下,使用读写锁,两个锁,就比较有用:
比如web项目,多个客户端,对一个服务端文件进行操作,可能有读,写(修改,删除)
如果全部都加一把锁:读读、读写、写写都是互斥的
实际期望提高效率:读读可以是并发,另外两个互斥
我们写的代码就可以是:

readLock.lock()
读文件
readLock.unlock()

writeLock.lock()
写操作
writeLock.unlock()

二、CAS

1.什么是CAS

英文全称为Compare and Swap:比较并交换
是JDK提供的一种乐观锁的实现,能够满足线程安全的条件下,以乐观锁的方式来修改变量

  1. 比较从主存读,和写回主存时,是否相等(比较)
  2. 如果比较相等,把工作内存修改的值写回主存;如果不相等,就不修改主存数据(交换)
  3. 返回修改操作的结果

2.CAS原理

1.JDK提供了一个unsafe的类,来执行CAS的操作
2.JDK中unsafe提供的CAS方法,又是基于操作系统提供的CAS方法(这个又是基于cpu提供的CAS硬件支持,且使用了lock加锁)

简单地说,unsafe是基于操作系统和cpu提供的CAS来实现的

3.CAS的应用

比较常见的:Java的Java.util.concurrent.atomic包下的都是使用了CAS来保证线程安全的++,- -,!flag这种操作

这里使用CAS vs synchronized
CAS效率高,synchronized效率低
原因:++,- -,!flag这种操作执行速度非常快,很快能得到执行

4.CAS的ABA问题

在没有引入版本号的情况下,CAS是基于变量的值,在读和写的时候来比较

读:主存读到工作内存
写:工作内存写回主存

多线程进阶(常见的锁策略、CAS以及Synchronized原理)_第2张图片
如何解决ABA问题?
引入版本号:每次修改操作,版本号+1,比较的时候,还需要比较读和写的时候,版本号是否一样
JDK中,提供了一个叫AtomicStampedReference的类,里边可以包装其他类(里边用成员变量设置为我们要修改的数据),里边提供了版本号管理

三、Synchronized原理

1.加锁工作过程

synchronized作用: 基于对象头加锁的方式,实现线程间的同步互斥 (对同一个对象进行加锁的多个线程,同一个时间只有一个线程获取到锁,相当于线程间获取锁,执行同步代码是互斥的)

对象头加锁:
多线程进阶(常见的锁策略、CAS以及Synchronized原理)_第3张图片

synchronized加锁操作,可能涉及对象头状态升级的过程(性能/效率从低到高):
无锁: 没有任何线程申请到该对象的锁
偏向锁: 第一次进入的线程,或是这个线程再次申请同一个对象锁
轻量级锁: CAS+自旋:出现线程冲突(竞争),但冲突概率比较小
重量级锁: 真实地进行加锁(使用操作系统mutex进行加锁),冲突比较严重

锁升级过程,但是不能够降级

原理:

1.JVM把synchronized这样的Java代码编译为class字节码之后,实际是一个monitor机制
monitorenter(相当于对象头的监视器锁:加锁)
…同步代码
monitorexit(退出:释放锁)
…(JVM执行一些指令)
monitorexit(退出:释放锁)

两个exit指令:因为同步代码可能出现异常
monitor中,还保存了加锁的次数
synchronized同一个线程重入申请这个对象锁,次数+1,释放锁其实是次数-1,等次数=0,就真实的释放锁

总结:
synchronized编译为class字节码,是基于monitor机制来实现对对象头加锁:一个monitor enter和两个monitorexit字节码指令保证即使出现异常也能释放锁,使用计数器来设置重入的次数

2.其他的优化操作

  1. 锁消除:前提:变量只有一个线程可以操作
    多线程进阶(常见的锁策略、CAS以及Synchronized原理)_第4张图片
  2. 锁粗化:前提:变量是多个线程可以使用,但一个线程多次连续调用synchronized方法
    多线程进阶(常见的锁策略、CAS以及Synchronized原理)_第5张图片

你可能感兴趣的:(多线程,java,多线程)