总结来说,cas(compare and swap)按照字面意思就可以理解,每次都从某块内存区域拿到旧值和期望值进行比较,如果相等就修改为新值。如果不相等则会自旋( While(! compareAndSwap(old,current) ),这里放一下我学习自旋锁的网址
https://blog.csdn.net/qq_34337272/article/details/81252853
当然自旋锁会有两个问题:
- 因为自旋锁是一种乐观锁,当期望值和内存值不相等时会一直保持自选状态,那么显然当线程竞争激烈时长时间自旋不成功则会占用cpu较高(可以通过设置自旋次数来解决)
- 无法重入,想象一下当持有这把锁的线程想再次获取该锁的时候,cas依旧会失败。(可以通过引入计数器来解决)
public void lock() {
Thread cur = Thread.currentThread();
if(cur == cas.get()){
//如果是该线程,那么计数器++
count++;
return;
}
//不是该线程,cas获取锁
while (!cas.compareAndSet(null, current)) {
// DO nothing
}
}
public void unlock() {
Thread cur = Thread.currentThread();
if (cur == cas.get()) {
if (count > 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
count--;
} else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
cas.compareAndSet(cur, null);
}
}
}
以下总结来自于
https://blog.csdn.net/vanchine/article/details/111772190?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242
AQS (AbstractQueuedSynchronizer)提供了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。 他维护了一个volatile int state ,线程通过修改(加/减指定的数量)state是否成功来决定当前线程是否成功获取到同步状态(也就是对state进行cas操作) ,同时还维护了一个FIFO队列(先进先出队列),当cas失败时,公平锁会选择将线程放入队列;而非公平锁则会不断自旋。
独占锁和共享锁
AQS支持两种获取同步状态的模式既独占式和共享式。独占式模式同一时刻只允许一个线程获取同步状态,而共享模式则允许多个线程同时获取。ReentrantLock就是以独占方式实现的互斥锁,ReadWriteLock的读锁是共享锁(显然读取数据并不会修改数据,所以没有必要独占),写锁是独占锁,Semaphore是一种共享锁(state设置为可进入的线程数)。
以ReentrantLock举例:
//指定锁类型
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//默认为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//实现公平锁
private ReentrantLock reentrantLock = new ReentrantLock(true);
Synchronized而言,是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
下面对一部分ReentranLock源码进行分析:
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
--FairSync
//公平锁中竞争锁
final void lock() {
//都按照FIFO来竞争锁
acquire(1);
}
--UnFairSync
//非公平锁中竞争锁
final void lock() {
· //任何线程都可以先通过CAS去抢占锁
if (compareAndSetState(0, 1))
//设置独占锁
setExclusiveOwnerThread(Thread.currentThread());
else
//抢占失败,按照FIFO来竞争锁
acquire(1);
}
--AbstractQueuedSynchronizer
//释放锁就一种实现
public final boolean release(int arg) {
if (tryRelease(arg)) {
// Node就是一个包含了线程信息的节点,放在了队列中
Node h = head;
if (h != null && h.waitStatus != 0)
//解除挂起状态
unparkSuccessor(h);
return true;
}
return false;
}
其中的acquire方法来源于AQS实现:
public final void acquire(int arg) {
//tryAcquire可以理解为:尝试直接获取独占锁
//tryAcquire失败之后将当前线程封装Node添加到AQS队列尾部(addWaiter)
///并继续循环获取锁(acquireQueued)
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
)
selfInterrupt();
}
--FairSync
protected final boolean tryAcquire(int acquires) {
...
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//hasQueuedPredecessors()首先判断是否有头尾节点
//然后判断是否有后继节点,最后判断后继节点是否为当前线程
// hasQueuedPredecessors()判断成功返回的是False
//return h != t && 是否有头尾节点
//( (s = h.next) == null || 是否有后继节点
// s.thread != Thread.currentThread() ); 是否为当前线程
//有则需要继续进行CAS
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} ...
}
--UnFairSync
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//获取status值,如果为0则表示仍然可以再CAS
if (c == 0) {
if (compareAndSetState(0, acquires)) {
//设置当前持有锁的线程
setExclusiveOwnerThread(current);
return true;
}
}
//status不为0,继续判断获取锁的线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
//如果是则status加1,增加重入次数
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果都不是则返回false
return false;
}
//TryAcquire失败则需要添加到队列尾
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//先去获取当前节点的prev(前驱)节点
final Node p = node.predecessor();
//前驱节点是head则再次尝试获取锁
if (p == head && tryAcquire(arg)) {
//获取锁之后设置head为当前node并清空node的线程信息和prev
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//获取锁失败判断是否需要挂起
if (shouldParkAfterFailedAcquire(p, node) &&
//前驱节点状态为SIGNAL,当前线程park阻塞
//LockSupport.park/unpark为线程提供一个类似开关的功能
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
//取消获取锁,主要就是设置节点waitStatus为cancelled,清空节点内容
//最主要的是unpark为下一个线程放行
cancelAcquire(node);
}
}