1.乐观锁
乐观锁
是一种乐观思想
,假定当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改(所以没有上锁)。写数据时,判断当前与期望值是否相同,如果相同则进行更新(更新期间加锁,保证是原子性的)。
Java中的
乐观锁
:
CAS
,比较并替换,比较当前值(主内存中的值),与预期值(当前线程中的值,主内存中值的一份拷贝)是否一样,一样则更新,否则继续进行CAS操作。
如下图所示,可以同时进行读操作,读的时候其他线程不能进行写操作。
![](https://img-blog.csdnimg.cn/direct/be3a3ae409064cc3bd357180980e79f1.png)
2.悲观锁
悲观锁
是一种悲观思想
,即认为写多读少,遇到并发写的可能性高,每次去拿数据的时候都认为其他线程会修改,所以每次读写数据都会认为其他线程会修改,所以每次读写数据时都会上锁。其他线程想要读写这个数据时,会被这个线程block,直到这个线程释放锁然后其他线程获取到锁。
Java中的
悲观锁
:
synchronized
修饰的方法和方法块、
ReentrantLock
。
如下图所示,只能有一个线程进行读操作或者写操作,其他线程的读写操作均不能进行
![](https://img-blog.csdnimg.cn/direct/0c2fdf3bff4f497c8e1e7707fa9c952e.png)
3.自旋锁
自旋锁
是一种技术:
为了让线程等待,我们只须让线程执行一个忙循环(自旋)。现在绝大多数的个人电脑和服务器都是多路(核)处理器系统,如果物理机器有一个以上的处理器或者处理器核心,能让两个或以上的线程同时并行执行,就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。
自旋锁
的优点:
避免了线程切换的开销。挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。
自旋锁
的缺点:
占用处理器的时间,如果占用的时间很长,会白白消耗处理器资源,而不会做任何有价值的工作,带来性能的浪费。因此自旋等待的时间必须有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程。
自旋
次数默认值:
10次,可以使用参数-XX:PreBlockSpin来自行更改。
自适应
自旋
:
自适应意味着自旋的时间不再是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。有了自适应自旋,随着程序运行时间的增长及性能监控信息的不断完善,虚拟机对程序锁的状态预测就会越来越精准。
Java中的
自旋锁
:
CAS操作中的比较操作失败后的自旋等待。
如下图
![](https://img-blog.csdnimg.cn/direct/8af7fe59af4245ca9e769f67b0e0aafb.png)
4. 可重入锁(递归锁)
可重入锁
是一种技术:
任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞。
可重入锁
的原理:
通过组合自定义同步器来实现锁的获取与释放。
再次获取锁:识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。获取锁后,进行计数自增。
释放锁:释放锁时,进行计数自减。
Java中的
可重入锁
:
ReentrantLock、synchronized修饰的方法或代码段。
可重入锁
的作用:
避免死锁。
如下图: