目录
一、锁策略
什么是锁策略,也就是根据不同的场景,设置不同的锁。这个锁应该要适应当前的场景,比如,这个锁是否要加重、是否可重入等等。
1.悲观锁、乐观锁
这是一个相对的锁策略,在加锁的时候,会预测当前这个锁的所冲突是大或者小,就会根据大或小决定锁的策略。
(1)悲观锁:预测到当前锁冲突概率大,后续要做的工作往往就很多,所以加锁的开销就更大(包括时间、系统资源等等)。
(2)乐观锁:预测当前锁冲突概率不大,后续就不需要做很多的工作,加锁开销就更小。
(3)像我们常使用的synchronized锁,支持自适应,也就是既属于悲观锁,也属于乐观锁。
synchronized会根据当前锁冲突的情况做出判断。当冲突概率低的时候,就会按照乐观锁的方式来执行,效率也就更快;当冲突的概率高时,就会按照悲观锁的方式来执行,效率更慢。
2.重量锁、轻量锁
这一组锁和上述的悲观、乐观和像,但是定义概念的出发点不同。
(1)重量锁:在加锁过程中,做的事情多
(2)轻量锁:在加锁的过程中,做的事情少
(3)synchronized既属于重量锁,又属于轻量锁。
3.自旋锁、挂起等待锁
这一组锁,也是和上述的锁有关联,可以说是在上述锁的基础上
(1)自旋锁:是实现轻量锁的一种典型实现方式
举例:在尝试获取锁时,锁已被其他线程获取,此时要如何等待锁。此时,自旋锁就会不停的循环等待锁的释放,在锁释放的第一时间,就可以拿到锁,拿到锁的效率更快了,但是会消耗很多cpu的资源。
(2)挂起等待锁:是实现重量锁的一种典型实现方式
当尝试加锁的时候,锁被占用了,也就是发生锁冲突,这个时候,不会像自旋锁那样一种等待,而是被挂起(进入阻塞状态),此时该线程不会参与调度。一直到这个锁被释放,系统才会去唤醒该线程,重新进入竞争锁的队列中,但是已经不是一直在等待锁,所以拿到锁的速度会更慢,但是更加节省cpu。
(3)像synchronized轻量级锁部分,是基于自旋锁实现;而重量级锁部分,基于挂起等待锁实现
上面的三组锁,在概念上面都有很大的相同之处。
4.可重入锁、不可重入锁
(1)可重入锁:同一个线程连续对同一个对象加锁多次,不会造成死锁
(2)不可重入锁:同一个线程对同一个对象加锁多次,会造成死锁
(3)像synchronized就是可重入锁
5.公平锁、非公平锁
(1)公平锁:严格按照先来后到的顺序来获取锁(也就是按照队列来获取)。哪一个线程等待的时间最长,哪个线程就拿到锁。
(2)非公平锁:n个线程,不按顺序,随机获取到锁(抢占式、随机获取的),和线程等待的时间没有关系。
(3)synchronized属于非公平锁
6.互斥锁、读写锁
(1)互斥锁:当一个线程对一个对象加锁之后,其他的线程再想对这个对象加锁,就一定会产生互斥(也就是阻塞)
(2)读写锁:是一种特殊的锁,分为:加读锁、加写锁、解锁。
当一个线程对一个对象加锁之后,其他线程根据加锁的类型是否要产生互斥
1)当该第一个线程加的锁是读锁,第二个线程也加读锁,是不会产生互斥的。(读锁和读锁之间,会产生互斥)
2)第一个线程是写锁,第二个线程也是写锁,也会产生互斥。(写锁和写锁之间,会产生互斥)
3)读锁和写锁之间,也会产生互斥
读写锁的优势,就是可以增加并发能力,提供程序执行的效率。
(3)像synchronized是互斥锁。
二、sychronized实现原理
我们的sychronized锁,即是悲观锁,也是乐观锁;即是重量锁,也是轻量锁;即是自选锁,也是挂起等待锁;还是可重入锁、非公平锁和互斥锁。正是因为它是一个“自适应”锁,在使用synchronized加锁时,就会有一系列的升级策略。
1.升级策略
升级的步骤大概是四步:未加锁状态(无锁)---》偏向锁---》轻量级锁---》重量级锁。而且这种升级是不可逆的。下面会侧重介绍什么是偏向锁。
(1)无锁状态
这个也就是没有加锁时,或者代码中还未执行到synchronized处
(2)偏向锁
当执行完了synchronized后,此时并不是真正的加锁,仅仅只是做了一个标记,当有冲突时,才会真正的加锁(也就是升级为轻量级锁后)
举例:当第一次synchronized时,只是轻轻的标记了一下,未真正加锁;此时,如果有其他线程也想尝试加锁(有冲突出现),此时该线程就会感到危机,就会真正的加锁。但是如果一种没有其他线程来尝试加锁,就只是标记,不会真正加锁,这样就可以提高效率,减少资源的消耗。
偏向锁是懒的一种体现,能不加锁,就不加锁。
(3)轻量级锁
当有冲突时,就会从偏向锁升级为轻量级锁。
(4)重量级锁
当冲突进一步加大时,就会从轻量级锁升级为重量级锁,开销进一步加大。
2.锁消除
(1)锁消除,是编译器的一种优化机制
(2)当代码中有加锁操作之后,编译器会根据当前的代码做出判断,如果该位置确实不需要真的加锁,就会自动将锁的操作给优化掉。例如:只有一个线程的代码中,使用了锁操作。
(3)编译器会保证这种操作前后是不会出错的,所以该优化出现的场景也比较少
3.锁粗化
在锁的粗化之前,有个词称为锁的粒度
(1)锁的粒度:在一个加锁范围内,包含的代码越多,锁的粒度就越粗;反之,锁的粒度就越细。
(2)做法:在一些代码中,需要频繁的加锁和解锁,编译器就会自动的把多个细粒度的锁,合并称一个粗粒度的锁。前提都是对同一个对象加锁
举例:像打电话汇报三个任务,是打一次电话汇报一个任务还是打一次汇报三个任务的效率快呢?那当然是打一次电话汇报三个任务快。所以jvm就会把多个单独的锁合并成一个锁。