学了几天python,辣条君始终不忘自己其实是个Javaer。来,跟着辣条君一起看看Java锁是如何分类的吧。
Java锁是为了保证并发过程中,数据维护的准确性。
乐观锁与悲观锁
乐观锁
:认为当前读取数据的时候,不会有线程去修改数据,所以不需要加锁。当更新数据的时候,首先查看数据和自己曾经记录的数据是否一致,如果一致,则更新之;如果不一致,采取一些手段,比如报错或者自旋(自旋后面会讲)。
举个例子,一个线程A读取账户余额时,不会加锁,读到20元,线程A账户记录更新为20元。然后线程A为账户余额增加5元,现在想把账户余额更新为25元,先去查看账户现在的数值为20元,和账户记录一致,就将账户余额直接更新为25元,同时将自己的账户记录更新为25元。过了一会儿,线程A又想给账户余额增加5元,于是拿着30元去更新账户余额。此时,发现账户余额为100元,和自己的账户记录(25元)不一致了,就报错(或者自旋),此次更新失败。
CAS
(比较交换)就是一种常见的乐观锁实现方案,java.util.concurrent.atomic
中的那些原子类就是通过CAS
算法实现的。为了保证更新的原子性,原子类最终实质上是通过JNL调用了CPU的原子操作。CAS
先天有两点不足:1、ABA
问题。2、长时间的自旋
会消耗过多的资源。
所以乐观锁多用于读数据多的场景,效率较高。
悲观锁
:认为无论自己进行什么操作,那一瞬间都会有其他线程来污染数据,所以一定要加锁。
悲观锁很好理解,不需要加什么例子了,synchronized
和Lock
都属于悲观锁。关于这两个类的使用,我想另写一篇博客,毕竟日常使用比较多嘛。悲观锁多用于写数据多的场景
自旋锁
上一节反复提到自旋,自旋究竟是个什么东东呢?
首先我们要知道,一个Java线程被阻塞,会放弃CPU使用权;被唤醒,会重新获得CPU使用权。这两个切换上下文的过程,是极其消耗资源的。如果,一个同步操作(线程占用锁)的时间极短,那需要用锁的线程可以先等一会儿,待会不用进行上下文切换,拿到锁直接执行,那岂不是极好的。这个等待操作就叫做自旋。
自旋操作一般会规定自旋次数,如果一定次数还是没有得到锁,那就放弃自旋,进行阻塞。为了更加提升效率,自适应自旋锁出现了,它不拘泥于固定的次数,而是根据以往经验,如果以前自旋一段时间可以得到锁,那么超过最大自旋数的时候,允许多自旋几次;如果以往经验总是失败,那么不一定非得到达最大自旋数,就直接进入自旋状态。
无锁、偏向锁、轻量级锁、重量级锁
根据切换资源消耗成本,可以将锁分为无锁、偏向锁、轻量级锁、重量级锁。
无锁
:就是不对资源加锁,例如上面讲到的CAS算法,只是在更新的时候进行一下比较判断就好。
偏向锁
:有一种理想的状态,一段时间内只有一个线程访问同步代码块,这样是不是连更新时比较的步骤都可以省略了。这种情况下可以挂上偏向锁,这样该线程在访问同步代码块的时候就不需要CAS
操作了。当,有其他线程来访问共享资源的时候,偏向锁自动升级为轻量级锁。如果没有线程来打扰,只有当虚拟机运行到全局安全点的时候才能撤销偏向锁。
轻量级锁
:当一个线程拥有轻量级锁,另一个线程想拥有这把锁,不会进入阻塞状态,而是先自旋,等待获得锁的机会。但是,当多个线程(至少两个)来获取这把锁时,这把锁会直接升级为重量级锁。
重量级锁
:当一个线程拥有重量级锁时,其他线程想要获取该锁,都会直接进入阻塞状态。在JDK1.6之前synchronized
机制使用的时重量级锁,1.6版本之后开始使用轻量级锁和偏向锁
公平锁和非公平锁
公平锁
:当多个线程请求获取锁时,根据请求的先后顺序放到一个队列里,然后按顺序获取锁。此时,线程从阻塞到唤醒是需要上下文切换的。保证公平性,但是效率可能较低。
非公平锁
:非公平锁,尝试被线程获取的时候,不一定从线程队列中获取,先看看此时有没有新的线程来获取本锁,如果有,直接把锁给该线程,不需要进行上下文切换。失去公平性,但是可能会提高效率。
可重入锁
可重入锁
是指同一个线程可以多次加同一把锁。ReentrantLock
和synchronized
都属于可重入锁。
public class MyTest {
// 方法嵌套
public synchronized void outThing() {
// do someting
innerThing();
}
public synchronized void innerThing() {
// do something
}
}
看上面这种情况,方法嵌套通过synchronized
机制2次获取了对象的锁(Monitor)。如果是非可重入锁,一定会发生死锁。
共享锁和独享锁
共享锁
:一个线程给共享资源加上共享锁后,其他线程还可以给这个共享资源加上其他的共享锁。比如常见的读锁。
独享锁
:一个资源被加上独享锁后,就不能添加其他锁了。比如常见的写锁。
共享锁和独享锁在mysql层面也是通用概念。
小结
有了这些Java锁的概念,再去看代码就方便多了。接下来会好好研究下synchronized
和ReentrantLock
。