在学习并发的过程中,会遇到很多种锁,如果理解不好,或有一些困惑。下面就对这些锁做一下总结,就当做一个笔记吧。
锁有如下种类:
(1)、公平锁 / 非公平锁
(2)、可重入锁 / 不可重入锁
(3)、独享锁 / 共享锁
(4)、互斥锁 / 读写锁
(5)、乐观锁 / 悲观锁
(6)、分段锁
(7)、偏向锁 / 轻量级锁 / 重量级锁
(8)、自旋锁
上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面的内容是对每个锁的名词进行一定的解释。
二、锁的介绍 1、公平锁 / 非公平锁公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁:非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于 ReentrantLock 来说,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。 对于 Synchronized 而言,也是一种非公平锁。由于其并不像 ReentrantLock 是通过 AQS 的来实现线程调度,所以并没有任何办法使其变成公平锁。
2、可重入锁 / 不可重入锁可重入锁: 广义上的可重入锁 指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock 和 synchronized 都是可重入锁
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
不可重入锁:不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下:
import java.util.concurrent.atomic.AtomicReference;
public class UnreentrantLock {
private AtomicReference owner = new AtomicReference();
public void lock() {
Thread current = Thread.currentThread();
//这句是很经典的“自旋”语法,AtomicInteger中也有
for (;;) {
if (!owner.compareAndSet(null, current)) {
return;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
上面代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。
把它变成一个可重入锁:
import java.util.concurrent.atomic.AtomicReference;
public class UnreentrantLock {
private AtomicReference owner = new AtomicReference();
private int state = 0;
public void lock() {
Thread current = Thread.currentThread();
if (current == owner.get()) {
state++;
return;
}
//这句是很经典的“自旋”式语法,AtomicInteger中也有
for (;;) {
if (!owner.compareAndSet(null, current)) {
return;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
if (current == owner.get()) {
if (state != 0) {
state--;
} else {
owner.compareAndSet(current, null);
}
}
}
}
在执行每次操作之前,判断当前锁持有者是否是当前对象,采用 state 计数,不用每次去释放锁。
ReentrantLock 中可重入锁实现
这里看非公平锁的锁获取方法:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {//就是这里
int nextc = c + acquires;
if (nextc 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
count--;
} else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
cas.compareAndSet(cur, null);
}
}
}
}
自旋锁与互斥锁
(1)、自旋锁与互斥锁都是为了实现保护资源共享的机制。
(2)、无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
(3)、获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。
自旋锁总结:
(1)、自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。
(2)、自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。
(3)、自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。
(4)、自旋锁本身无法保证公平性,同时也无法保证可重入性。
(5)、基于自旋锁,可以实现具备公平性和可重入性质的锁。