文章目录
一、常见的锁策略
1.乐观锁 VS 悲观锁
悲观锁:
大多数时间看,存在线程冲突(悲观地看待问题),每次都先加锁,再释放锁
乐观锁:
大多数时间看,没有线程冲突的(乐观地看待问题),每次都不加锁(Java层面看),每次都直接执行修改数据的操作,返回修改是否成功的结果,程序自行处理逻辑
Synchronized初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略
乐观锁的一个重要功能就是要检测出数据是否发生访问冲突,我们可以引入一个”版本号“来解决
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语言层面看代价小,也叫轻量级锁
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提供的一种乐观锁的实现,能够满足线程安全的条件下,以乐观锁的方式来修改变量
- 比较从主存读,和写回主存时,是否相等(比较)
- 如果比较相等,把工作内存修改的值写回主存;如果不相等,就不修改主存数据(交换)
- 返回修改操作的结果
2.CAS原理
1.JDK提供了一个unsafe的类,来执行CAS的操作
2.JDK中unsafe提供的CAS方法,又是基于操作系统提供的CAS方法(这个又是基于cpu提供的CAS硬件支持,且使用了lock加锁)
3.CAS的应用
比较常见的:Java的Java.util.concurrent.atomic包下的都是使用了CAS来保证线程安全的++,- -,!flag这种操作
4.CAS的ABA问题
在没有引入版本号的情况下,CAS是基于变量的值,在读和写的时候来比较
如何解决ABA问题?
引入版本号:每次修改操作,版本号+1,比较的时候,还需要比较读和写的时候,版本号是否一样
JDK中,提供了一个叫AtomicStampedReference的类,里边可以包装其他类(里边用成员变量设置为我们要修改的数据),里边提供了版本号管理
三、Synchronized原理
1.加锁工作过程
synchronized作用: 基于对象头加锁的方式,实现线程间的同步互斥 (对同一个对象进行加锁的多个线程,同一个时间只有一个线程获取到锁,相当于线程间获取锁,执行同步代码是互斥的)
synchronized加锁操作,可能涉及对象头状态升级的过程(性能/效率从低到高):
无锁: 没有任何线程申请到该对象的锁
偏向锁: 第一次进入的线程,或是这个线程再次申请同一个对象锁
轻量级锁: CAS+自旋:出现线程冲突(竞争),但冲突概率比较小
重量级锁: 真实地进行加锁(使用操作系统mutex进行加锁),冲突比较严重
原理:
1.JVM把synchronized这样的Java代码编译为class字节码之后,实际是一个monitor机制:
monitorenter(相当于对象头的监视器锁:加锁)
…同步代码
monitorexit(退出:释放锁)
…(JVM执行一些指令)
monitorexit(退出:释放锁)
总结:
synchronized编译为class字节码,是基于monitor机制来实现对对象头加锁:一个monitor enter和两个monitorexit字节码指令保证即使出现异常也能释放锁,使用计数器来设置重入的次数
2.其他的优化操作
- 锁消除:前提:变量只有一个线程可以操作
- 锁粗化:前提:变量是多个线程可以使用,但一个线程多次连续调用synchronized方法