网站域名的选择方法,短网址生成怎么使用,中国五大网站建设公司,建设基金会网站转载自 一文带你理解Java中Lock的实现原理
当多个线程需要访问某个公共资源的时候#xff0c;我们知道需要通过加锁来保证资源的访问不会出问题。java提供了两种方式来加锁#xff0c;一种是关键字#xff1a;synchronized#xff0c;一种是concurrent包下的lock锁。syn…转载自 一文带你理解Java中Lock的实现原理
当多个线程需要访问某个公共资源的时候我们知道需要通过加锁来保证资源的访问不会出问题。java提供了两种方式来加锁一种是关键字synchronized一种是concurrent包下的lock锁。synchronized是java底层支持的而concurrent包则是jdk实现。关于synchronized的原理可以阅读再有人问你synchronized是什么就把这篇文章发给他。
在这里我会用尽可能少的代码尽可能轻松的文字尽可能多的图来看看lock的原理。
我们以ReentrantLock为例做分析其他原理类似。
我把这个过程比喻成一个做菜的过程有什么菜做法如何 我先列出lock实现过程中的几个关键词计数值、双向链表、CAS自旋 使用例子
import java.util.concurrent.locks.ReentrantLock;public class App {public static void main(String[] args) throws Exception {final int[] counter {0};ReentrantLock lock new ReentrantLock();for (int i 0; i 50; i){new Thread(new Runnable() {Overridepublic void run() {lock.lock();try {int a counter[0];counter[0] a 1;}finally {lock.unlock();}}}).start();}// 主线程休眠等待结果Thread.sleep(5000);System.out.println(counter[0]);}
}在这个例子中开50个线程同时更新counter。分成三块来看看源码初始化、获取锁、释放锁 实现原理
ReentrantLock() 干了啥 /*** Creates an instance of {code ReentrantLock}.* This is equivalent to using {code ReentrantLock(false)}.*/public ReentrantLock() {sync new NonfairSync();}在lock的构造函数中定义了一个NonFairSync
static final class NonfairSync extends Sync NonfairSync 又是继承于Sync
abstract static class Sync extends AbstractQueuedSynchronizer一步一步往上找找到了 这个鬼AbstractQueuedSynchronizer简称AQS最后这个鬼又是继承于AbstractOwnableSynchronizer(AOS)AOS主要是保存获取当前锁的线程对象代码不多不再展开。 最后我们可以看到几个主要类的继承关系。 锁的类的继承关系
FairSync 与 NonfairSync的区别在于是不是保证获取锁的公平性因为默认是NonfairSync我们以这个为例了解其背后的原理。
其他几个类代码不多最后的主要代码都是在AQS中我们先看看这个类的主体结构。
AbstractQueuedSynchronizer是个什么 再看看Node是什么 看到这里的同学是不是有种热泪盈眶的感觉这尼玛不就是双向链表么我还记得第一次写这个数据结构的时候发现居然还有这么神奇的一个东西。
最后我们可以发现锁的存储结构就两个东西:双向链表 int类型状态。 需要注意的是他们的变量都被transient和volatile修饰。 一个int值一个双向链表是如何烹饪处理锁这道菜的呢Doug Lea大神就是大神我们接下来看看如何获取锁
lock.lock()怎么获取锁
/*** Acquires the lock.*/
public void lock() {sync.lock();
}
可以看到调用的是NonfairSync.lock() 看到这里我们基本有了一个大概的了解还记得之前AQS中的int类型的state值这里就是通过CAS乐观锁去修改state的值。lock的基本操作还是通过乐观锁来实现的。
获取锁通过CAS那么没有获取到锁等待获取锁是如何实现的我们可以看一下else分支的逻辑acquire方法
public final void acquire(int arg) {if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}这里干了三件事情 tryAcquire会尝试再次通过CAS获取一次锁。 addWaiter将当前线程加入上面锁的双向链表等待队列中 acquireQueued通过自旋判断当前队列节点是否可以获取锁。
addWaiter 添加当前线程到等待链表中 可以看到通过CAS确保能够在线程安全的情况下将当前线程加入到链表的尾部。 enq是个自旋上述逻辑有兴趣的可以翻翻源码。
acquireQueued 自旋CAS尝试获取锁 可以看到当当前线程到头部的时候尝试CAS更新锁状态如果更新成功表示该等待线程获取成功。从头部移除。 每一个线程都在自旋CAS
最后简要概括一下获取锁的一个流程 获取锁流程
lock.unlock() 释放锁
public void unlock() {sync.release(1);
}可以看到调用的是NonfairSync.release() 最后有调用了NonfairSync.tryRelease() 基本可以确认释放锁就是对AQS中的状态值State进行修改。同时更新下一个链表中的线程等待节点。 总结 lock的存储结构一个int类型状态值用于锁的状态变更一个双向链表用于存储等待中的线程 lock获取锁的过程本质上是通过CAS来获取状态值修改如果当场没获取到会将该线程放在线程等待链表中。 lock释放锁的过程修改状态值调整等待链表。 可以看到在整个实现过程中lock大量使用CAS自旋。因此根据CAS特性lock建议使用在低锁冲突的情况下。目前java1.6以后官方对synchronized做了大量的锁优化偏向锁、自旋、轻量级锁。因此在非必要的情况下建议使用synchronized做同步操作。