问题:
1、Java中什么是锁,为什么要用锁?
2、Java中有哪些锁?
3、这些锁分别是怎么实现的?
4、这些锁的使用场景?
前言:
CAS:Compare And Swap(比较与交换)是一种无锁算法,在不是有锁(没有线程被阻塞)的情况下实现多线程直接变量的同步。CAS算法涉及三个操作数:
a、需要读写的内存值V
b、进行比较的值A
c、要写入的新值B
当前仅当V的值等于A时,CAS通过原子方式用新值B更新V的值,否则不会执行任何操作。
AQS: AbstractQueuedSynchronizer可以理解为同步锁的实现抽象类,它采用先进先出队列、结合CAS、Condition实现不同锁的机制。下面介绍的很多锁都是基于AQS实现的。
探索:
1、Java中什么是锁,为什么要用锁?
锁可以理解成把资源加上锁,让其他请求者使用不了这个资源。
为什么用锁?现实生活中我们也有用到锁,用锁当然是为了安全,Java用锁当然也是为了安全,Java多线程如果同时对一块资源进行操作时,会造成数据的不一致导致结果没有达到我们的预期,这是非常不安全的。所以在使用多线程对同一资源操作时,需要加上锁,大致的原理就是:当多线程对一块资源同时操作时,首先需要先请求获得对这块资源操作的权限,即锁,如果这块资源没有被其他线程加锁,那么当前线程就获得这块资源的操作锁,当前线程可以对这块资源进行操作(读写等),其他线程如果此时对这块资源请求操作锁时,就会被挂起,直到当前线程释放了锁,其他线程才能获得这块资源的操作锁。锁其实主要应用于与并发操作(包括多线程和多进程),单线程不需要使用锁。
大致原来是这样,说起来好像很简单,但是代码层的实现还是很复杂的,所以草根会用时间对锁一一进行解读
2、Java中有哪些锁?
Java中有哪些锁呢?我们最常用的便是synchronized,它属于什么锁呢?
对于Java有哪些锁,草根也不一定知晓的全面,以下就是草根学习中知晓的:
a、乐观锁和悲观锁
乐观锁:乐观锁是在读数据时,认为没有其他线程在使用数据,所以读取没有加锁,只有在写数据时判断是否被其他线程更新过,如果其他线程没有更新过那么将自己的输入写入成功,如果被其他线程更新过,则根据不同的实现规则执行不同的操作(自旋重试或者报错)。
悲观锁:悲观锁是在读写数据时,都认为有其他线程在使用数据,所有都会先加上锁在执行读写操作,确保数据不被其他线程修改。
b、自旋锁(CAS)和适应性自旋锁
自旋锁:指的是某线程操作成功,通过自旋操作不释放CPU时间片(循环判断)来实现。自旋锁可以解决获得锁的时间较短时切换线程和恢复线程带来的开销,但是如果长时间得不到锁会导致CPU时间片得不到释放,所以自旋需要有次数的限定,可以使用-XX:PreBlockSpin来知道自旋次数,默认是10次。
适应性自旋锁:适应性自旋锁的自旋次数是不固定的,它由前一次在同一个锁上的自旋时间和锁的持有者的状态共同决定。如果同一个锁对象上,自旋等待刚刚获得过锁,且持有这个锁的线程正在运行,那么虚拟机会认为自旋获得锁成功率很高,所以允许自旋的时间相对较长;反之如果同一锁对象上自旋很少获得锁,那么虚拟机会认为自旋的成功率很低,为了不浪费CPU资源,会直接阻塞当前线程。
c、共享锁和排它锁(基于AQS实现)
共享锁:指某一个锁可以被多个线程锁持有。如果线程T对数据D加上共享锁之后,其他线程只能对数据D加共享锁,不能加排它锁。获得共享锁的线程智能读数据不能修改数据。
排它锁:指某一个锁只能被一个锁持有。如果线程T对数据D加上排它锁之后,其他线程对数据D不能加任何类型的锁。获得排它锁的线程可以读写数据。
d、公平锁和非公平锁(基于AQS实现)
公平锁:指多线程按照申请锁的顺序来获取锁(采用先进先出队列),线程申请锁时直接加入队列中,每次都是队首的线程获得锁,执行完释放锁之后,出队,接着又从队首的线程获得锁,这样循环执行。公平锁的优点是等待获得锁的线程不会饿死,缺点是相对非公平锁吞吐量比较低,排在队首后面的线程都会阻塞,而且由于需要阻塞和唤醒锁所以相比非公平锁开销大。
非公平锁:指多线程申请锁时直接尝试获取锁,如果获取不到才会加入队列中等待,如果当前锁没被占用,那么直接获得锁,所以非公平锁存在可能会后申请锁的线程先获得所。非公平锁的有点是可以减少阻塞唤醒线程的开销,相对公平锁吞吐量比较大,缺点是可能有线程很久之后才获得所甚至出现饿死现象。
e、可重入锁和非可重入锁(基于AQS实现)
可重入锁:指同一线程当前获得了锁L,未释放的情况下当再次申请锁L时,可以直接获得锁L而不用阻塞等待,这样可以避免死锁。
非可重入锁:指同一线程当前获得了锁L,未释放的情况下当再次申请锁L时,会阻塞等待前一次锁L的释放,这样很容易出现死锁。
f、无锁、偏向锁、轻量级锁、重量级锁
这些锁其实是synchronized在虚拟机实现时的不同状态,状态依次从无锁—》偏向锁—》轻量级锁—>重量级锁升级。
无锁:无锁没有对资源进行锁定,所有线程都能访问并修改统一资源,但同时只有一个线程能修改成功,它在修改时是在循环内进行的,一直尝试修改直到修改成功。修改时如果没有冲突那么修改成功,如果有冲突就循环尝试直到成功。
偏向锁:指一段同步代码一直被一个线程访问,那么这个线程自动获得锁,减少获得锁的开销。
当一个线程访问同步代码块并获取锁时,会在Mark Word中存储偏向锁的线程ID,在线程进入和退出同步块时不再通过CAS来加锁和解锁,而是检测Mark Word中是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取和释放依赖多次CAS操作,而偏向锁只需要在置换线程ID的时候依赖一次CAS操作即可。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主导释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码在执行),它会首次暂停拥有偏向锁的线程,判断锁对象是否处于锁定状态。撤销偏向锁后恢复到无锁或轻量级锁的状态。
轻量级锁:指当锁是偏向锁时,被其他线程访问,偏向锁会升级为轻量级锁,其他线程会通过自旋的形式尝试获得锁,不会阻塞,从而提高性能。
代码块进入同步块时,如果同步对象锁状态为无锁状态,虚拟机首先将当前线程的栈帧中建一个名为锁记录的空间,用于存储锁对象目前Mark Word的拷贝,然后拷贝对象头中Mark Word到锁记录中。
如果轻量级锁的更新操作成功了,那么线程就拥有了该对象的锁,并且对象的Mark Word的锁标志位设置为00,表示此对象处于轻量级锁定状态。
如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。
如果当前只有一个等待线程,那该线程通过自旋进行等待。但当自旋超过一定次数,或者一个线程在持有锁一个在自旋,又来第三个访问时,轻量级升级为重量级锁。
重量级锁:轻量级锁升级为重量级锁时,锁标志位10,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。
综上所述,偏向锁通过对比Mark Word解决加锁问题,避免指向CAS操作。轻量级锁通过CAS和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁将拥有锁的线程之外的线程都阻塞。
3、锁的实现原理
a、乐观锁和悲观锁的实现原理
a1、乐观锁的实现原理
乐观锁在Java中用无锁编程来实现,最常用的是CAS算法,Atomic系列就是基于CAS实现的,我们来看下AtomicInteger的实现:
//---------------------AtomicInteger
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
//----------------Unsafe
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
上述代码中AtomicInteger是基于Unsafe类实现,Unsafe类调用的是native层的代码,这里我们只讨论Java层的。
几个关键的字段:
unsafe:可以理解成类实例对象内存操作的实现类
valueOffset:AtomicInteger实例对象内存中存储值value的偏移量
value:AtomicInteger实例对象存储的Int值
读取操作get方法即获取值的方法,这里并没加锁和自旋的机制,
写操作比如上述的getAndIncrement的时候调用了unsafe的getAndAddInt方法,getAndAddInt采用循环的方式不断从内存中读取值(getIntVolatile),并使用compareAndSwapInt方法比较值是否相等,相等这进行写入操作,失败则继续循环尝试。
a2、悲观锁的实现与非公平锁的原理一样,请到非公平锁的部分查看。
b、自旋锁(CAS)和适应性自旋锁的实现原理
自旋锁CAS的实现原来参考乐观锁的实现。
适应性自旋锁的实现原理草根后面研究虚拟机之后再做说明。
c、共享锁和排它锁的实现原理
共享锁和排它锁的实现原理这里用ReentrantReadWriteLock类的实现的进行说明
c1、共享锁的实现原理
//--------------------ReentrantReadWriteLock
public static class ReadLock implements Lock, java.io.Serializable {
public void lock() {
//读时请求共享锁,调用AQS的acquireShared方法
sync.acquireShared(1);
}
}
//--------------------AbstractQueuedSynchronizer
public final void acquireShared(int arg) {
//调用tryAcquireShared尝试获取共享锁,由子类实现,这里是ReentrantReadWriteLock的内部类Sync
if (tryAcquireShared(arg) < 0)
//尝试获取共享锁失败,表示有排他锁,调用doAcquireShared处理共享锁
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//将共享锁加入队列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
//循环自旋进行加锁
for (;;) {
//获取上一个节点
final Node p = node.predecessor();
if (p == head) {
//如果上一节点是队首,则尝试请求获取共享锁
int r = tryAcquireShared(arg);
if (r >= 0) {
//获取共享锁成功,则重置队列,并把当前节点作为队首
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//不是队首,表示还有其他节点在排队等待获得共享锁,此时调用unsafe的part方法挂起线程,等待释放排它锁
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//--------------------ReentrantReadWriteLock 内部类Sync
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
//获取当前请求获取共享锁的线程
Thread current = Thread.currentThread();
//获取当前锁的状态及请求锁的数量
int c = getState();
//判断其他线程是否持有排它锁
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
//其他线程持有排它锁,请求共享锁失败
return -1;
//获取共享锁的数量
int r = sharedCount(c);
//判断是否没有排它锁且共享锁的数量没有超过最大值且CAS成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//自旋获取共享锁成功
if (r == 0) {
//请求共享锁的数量为0
//设置第一个共享锁的线程
firstReader = current;
//设置第一个共享锁的线程请求共享锁的数量
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//第一个共享锁线程等于当前线程
//设置第一个共享锁的线程请求共享锁的数量+1
firstReaderHoldCount++;
} else {
//获取上一次请求共享锁的线程和数量持有者
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
//上一次请求共享锁的线程和数量持有者为空或者不等于当前线程,则设置上一次请求共享锁的线程和数量持有者
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
//上一次请求共享锁的线程和数量持有者请求的共享锁次数等于零,则设置直接设置
readHolds.set(rh);
//上一次请求共享锁的线程和数量持有者请求的共享锁次数加1
rh.count++;
}
//请求共享锁成功
return 1;
}
//这个操作与tryAcquireShared差不多一样,不做说明,感兴趣的读者可以自己阅读
return fullTryAcquireShared(current);
}
共享锁的实现原理其实就是如果没有其他线程占有排他锁的情况进行自旋操作,并设置请求持有共享锁的数量;如果有其他线程占有排他锁,那么请求共享锁失败,会将当前线程加入队列中等待其他线程是否排它锁。
c2、排它锁的实现原理
//--------------------ReentrantReadWriteLock
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
public void lock() {
//请求排它锁,调用的的AQS的acquire请求
sync.acquire(1);
}
}
//--------------------AbstractQueuedSynchronizer
public final void acquire(int arg) {
//调用tryAcquire(这个方法由子类实现,这里是ReentrantReadWriteLock的内部类Sync)尝试不加锁请求排它锁,如果失败,那么会调用acquireQueued方法加入队列中进行阻塞等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//--------------------ReentrantReadWriteLock的内部类Sync
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
//获取当前请求排它锁的线程
Thread current = Thread.currentThread();
//获取当前锁状态,即锁持有者的数量
int c = getState();
//获取持有排它锁的数量
int w = exclusiveCount(c);
if (c != 0) {
//如果持有锁总数量不为0
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
//如果持有排它锁的数量为0或者当前线程与持有锁的线程不相等
//意思是其他线程持有共享锁,而当前线程没有持有共享锁
//所以请求排他锁失败
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
//如果持有排它锁的数量和当前请求的数量超过总数量,则抛出异常
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//到这一步表示是当前线程已持有锁,不需要等待就可以实现自动获取锁
//获取排他锁成功,并设置持有排他锁的数量
setState(c + acquires);
return true;
}
//判断写是否阻塞(公平锁写阻塞,非公平锁不阻塞,这里默认采用的是非公平锁)或者CAS操作是否成功
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
//写阻塞或者CAS失败,则加入队列中等待
return false;
//获取排它锁成功,并设置持有排它锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
排它锁的实现原理就是先尝试不加锁的情况下请求排它锁,如果当前线程已经持有锁了那么自动获得排它锁;如果采用非公平策略CAS操作成功,那么获得排它锁成功,否则加入队列中等待。
不知有没有读者看了共享锁和排它锁有没有这样的疑问:尝试请求获得锁时都有判断锁的持有数量是否超出最大数量,那么持有锁的总数量、共享锁的数量、排它锁的数量最大值是多少?
持有锁的总数量的最大值是Int的最大值;
共享锁和排拖锁的总数量是16位的最大值
持有锁的数量、共享锁的数量、排拖锁的数量是复用了state这个字段,state的值就是持有锁数量(包括共享锁和排它锁),state的高16位的值就是持有共享锁的数量,state的低16位的值就是持有排它锁的数量,代码如下:
--------------------ReentrantReadWriteLock
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
//共享锁的数量位移量16,即高16位
static final int SHARED_SHIFT = 16;
//共享锁数量的加减单位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//共享锁总数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//排它锁总数量
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
//计算当前持有共享锁的数量
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
//计算当前持有排它锁的数量
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
//--------------------AbstractQueuedSynchronizer
/**
锁状态,及持有锁的数量
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
d、公平锁和非公平锁的实现原理
公平锁和非公平锁的实现原理这里使用ReentrantLock类进行说明
d1、公平锁的实现原理
//------------------ReentrantLock
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
//请求公平锁,调用AQS的acquire请求公平锁
acquire(1);
}
}
//--------------------AbstractQueuedSynchronizer
public final void acquire(int arg) {
//调用tryAcquire(由子类实现,这里是ReentrantLock内部类FairSync )尝试不加锁的情况下获取公平,如果获取失败则调用acquireQueued方法将线程加入队列中等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//------------------ReentrantLock内部类FairSync
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
//获取请求公平锁的当前线程
final Thread current = Thread.currentThread();
//获取当前锁的状态,即持有锁的数量
int c = getState();
if (c == 0) {
//如果持有锁的数量为0,即表示资源没有被占用
/*调用hasQueuedPredecessors判断是否有其他线程在队列中排队,如果没有其他线程在队列中且CAS操作成功,
那么请求获得锁成功,并设置持有锁为当前线程
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
/*如果持有锁的数量不为0,且持有锁的线程等于当前线程,
那么表示可重入,自动获得锁
*/
int nextc = c + acquires;
if (nextc < 0)
//如果持有锁的数量超过的Int的最大值那么抛出异常
throw new Error("Maximum lock count exceeded");
//持有锁的数量加1并更新
setState(nextc);
//获得锁成功
return true;
}
//锁被其他线程占用,返回失败
return false;
}
上述公平锁的实现原理是先尝试采用不加锁(CAS和可重入机制)且队列中没有待定获取锁的线程请求获得锁,如果获取成功就不需要加入队列中阻塞等待,如果获取失败才加入队列中阻塞等待。
d2、非公平锁的实现原理
//------------------ReentrantLock
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
//请求非公平锁先进行CAS操作成功,那么直接获得锁,并将锁的持有者设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
//请求非公平锁先进行CAS操作失败,调用AQS的acquire方法请求非公平锁
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
//AQS的acquire调用tryAcquire先尝试不加锁获取非公平锁
//调用父类Sync的nonfairTryAcquire尝试不加锁获取非公平锁
return nonfairTryAcquire(acquires);
}
}
//--------------------AbstractQueuedSynchronizer
public final void acquire(int arg) {
//调用tryAcquire(由子类实现,这里是ReentrantLock内部类FairSync )尝试不加锁的情况下获取公平,如果获取失败则调用acquireQueued方法将线程加入队列中等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//------------------ReentrantLock内部类Sync
final boolean nonfairTryAcquire(int acquires) {
//当前请求获取非公平锁的线程
final Thread current = Thread.currentThread();
//获取当前锁的状态,即持有锁的数量
int c = getState();
if (c == 0) {
//如果持有锁的数量为0,即当前资源没有被占用
if (compareAndSetState(0, acquires)) {
//CAS操作成功,表示获取非公平锁成功
//设置锁的持有者为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果持有锁的数量不为0,且持有锁的线程等于当前线程,即锁可重入策略
//增加持有锁的数量
int nextc = c + acquires;
if (nextc < 0) // overflow
//如果持有锁的数量超过了Int的最大值,抛出异常
throw new Error("Maximum lock count exceeded");
//更新锁状态即持有锁的数量
setState(nextc);
//返回获得锁成功
return true;
}
//锁被其他线程占用,返回获得锁失败
return false;
}
上述非公平锁的实现原理是先尝试采用不加锁(CAS和可重入机制)请求获得锁,如果获取成功就不需要加入队列中阻塞等待,如果获取失败才加入队列中阻塞等待。
公平锁与非公平锁的区别就是判断队列中是否有等待线程
e、可重入锁和非可重入锁的实现原理
可重入锁的实现原理在排它锁、公平锁和非公平锁中有体现,这里不做说明
非可重入锁的实现原理草根在Java层没有看到响应的实现类,有读者有发现烦请告知一声,谢谢。
f、无锁、偏向锁、轻量级锁、重量级锁的实现原理
这块属于虚拟机的实现,草根后续研读虚拟机代码之后再做说明。
4、锁的使用场景
自旋锁:适用于获得锁的时间短且读比写的多
乐观锁:适用于获得锁的时间短且读比写的多
悲观锁:适用于获得锁的时间较长且写比读的多
公平锁:适用于对获得锁的顺序有要求吞吐量不高
非公平锁:适用于对获得锁的顺序没有要求且吞吐量高
共享锁:适用于读比写的多
排它锁:适用于写比读的多
可重入锁:Java中一般都采用可重入锁防止死锁
非可重入锁:Java层没有看到有相应的代码,现实中也没有使用到过,所以未知
集合上述的探索,草根做个总结:
synchronized采用了自旋锁(CAS),它属于排它锁、非公平锁、可重入锁、悲观锁
ReentrantLock也采用了自旋锁(CAS),它默认采用非公平锁,使用时可以指定是否采用公平锁还是非公平锁,同时属于排他锁、可重入锁、悲观锁
ReentrantReadWriteLock也采用了自旋锁(CAS),它默认采用非公平锁,使用时可以指定是否采用公平锁还是非公平锁,它有共享锁和排他锁的实现策略,属于可重入锁,共享锁同时属于乐观锁,排它锁同时属于悲观锁
Amtoic系列属于自旋锁(CAS),属于乐观锁和自旋锁
当前还有其他的实现锁,后面研究之后再做补充。
以上属于个人自行探索,如有不对之处,望指正。
网友评论