JAVA并发之ReentrantLock原理解析
Java从版本5开始,在java.util.concurrent.locks包内 给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可以抛弃synchronized关键字了。
在这些锁实现之前,如果我们想实现锁的功能,只能通过synchronized关键字,例子如下:private static volatile int value; public static void main() { Runnable run = new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { increaseBySync(); } } }; Thread t1 = new Thread(run); Thread t2 = new Thread(run); t1.start(); t2.start(); while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(value); } private static synchronized int increaseBySync() { return value++; }
有了ReentrantLock之后,我们可以这样写:private static volatile int value; private static Lock lock = new ReentrantLock(); public static void main(String[] args) { testIncreaseWithLock(); } public static void main() { Runnable run = new Runnable() { @Override public void run() { try { lock.lock(); for (int i = 0; i < 1000; i++) { value++; } } finally { lock.unlock(); } } }; Thread t1 = new Thread(run); Thread t2 = new Thread(run); t1.start(); t2.start(); while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(value); }
以上两段代码都可以实现value值同步自增1,并且value都能得到正确的值2000。下面我们就来分析下ReentrantLock有哪些功能以及它底层的原理。可重入锁
从名字我们就可以看出ReentrantLock是可重入锁。可重入锁是指当某个线程已经获得了该锁时,再次调用lock()方法可以再次立即获得该锁。
举个例子,当某个线程在执行methodA()时,假设已经获得了锁,这是当它执行到methodB()时可以立即获得methodB里面的锁,因为两个方法是调用的同一把锁。private static Lock lock = new ReentrantLock(); public static void methodA(){ try{ lock.lock(); //dosomething methodB(); }finally{ lock.unlock(); } } public static void methodB(){ try{ lock.lock(); //dosomthing }finally{ lock.unlock(); } }
synchronized也是可重入锁,有兴趣的同学可以自己写个例子测试下。公平锁
通过源码我们可以看到ReentrantLock支持公平锁,并且默认是非公平锁。//ReentrantLock源码 public ReentrantLock() { sync = new NonfairSync(); } //ReentrantLock源码 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
下面是非公平锁和公平锁的lock()方法实现,从两个lock()方法我们可以看到,它们的不同点是在调用非公平锁的lock()方法时,当前线程会尝试去获取锁,如果获取失败了则调用acquire(1)方法入队列等待;而调用公平锁的lock()方法当前线程会直接入队列等待(acquire方法涉及到AQS下面会讲到)。//ReentrantLock源码,非公平锁 final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } //ReentrantLock源码,公平锁 final void lock() { acquire(1); }
而synchronized是一个非公平锁。超时机制
ReentrantLock还提供了超时机制,当调用tryLock()方法,当前线程如果获取锁失败会立刻返回;而当调用带参tryLock()方法时,当前线程如果在设置的timeout时间内未获得锁,也会立刻返回。而这些功能背后主要是依赖AQS实现的。//ReentrantLock源码 public boolean tryLock() { return sync.nonfairTryAcquire(1); } //ReentrantLock源码 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
synchronized没有这个功能。可被中断
ReentrantLock有一个lockInterruptibly()方法,它会最终调用AQS的两个方法:
AQS方法一中如果当前线程被中断则抛出InterruptedException,否则尝试去获取锁,获取成功则返回,获取失败则调用aqs方法二doAcquireInterruptibly()。
AQS方法二中在for循环线程自旋中也会判断当前线程是否被标记为中断,如果是也会抛出InterruptedException。
doAcquireInterruptibly()的细节我们在下面讲解AQS的时候会重点介绍,它和doAcquire()方法很类似,唯一区别是会抛出InterruptedException。//lock方法 public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } //aqs方法一 public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } //aqs方法二 private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }AQS(AbstractQueueSynchronizer)
AQS是一个基于队列的同步器,它是一个抽象类,主要提供了多线程获取锁时候的排队等待和激活机制,ReentrantLock内部有两个基于AQS实现的子类,分别针对公平锁和非公平锁做了支持。
下面我们以公平锁为例,讲解下ReentrantLock是如何依赖AQS实现其功能的。获得锁涉及到的主要源代码和解释如下://AQS源码,公平锁的lock()方法会直接调用该方法 //这里当前如果获取失败会调用acquireQueued方法 //addWaiter方法主要是将当前线程加入AQS内部队列的尾部 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //ReentrantLock中实现公平锁的AQS子类的方法 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //c == 0表示当前AQS为初始状态,可以尝试获取锁, 如获取成功则返回true if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 只有当前线程是已经获取了该锁的线程才能再次获取锁(可重入锁)并返回true else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //返回false获取失败 return false; } //AQS源码 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); //这里如果当前线程对应的队列里的Node的前置Node是head,则尝试获取锁,并成功返回 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //shouldParkAfterFailedAcquire方法标记当前线程Node的前置Node的waitStatus为SIGNAL,意思是当你从队列移除时记得要唤醒我哦 //parkAndCheckInterrupt方法会让当前线程挂起,停止自旋,免得白白浪费CPU资源 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
假设现在有线程A、B、C同时去获取同一把锁,大概的流程是这样的,这里假设线程A获得锁并成功返回,线程B和C则依次调用上面的方法,进入AQS的等待队列并最终被挂起。
这时线程A做完自己该做的事情了,它在finally块中调用了unlock方法,这时我们再看下相关的源代码。//AQS源码,当前线程在释放锁的同时,会判断它所在Node的waitStatus有没有被它的后继结点标记,如果被标记了,那就唤醒后继结点对应的线程 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } //AQS源码,主要关注最下面LockSupport.unpark(s.thread),唤醒后继结点线程 private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
线程A释放锁时,会唤醒head结点的后继结点也就是线程B,此时线程B就可以继续for循环里面自旋并成功获得锁了。unsafe相关
之前介绍AtomicInteger的时候提到Unsafe对象,AtomicInteger用到了Unsafe对象的CAS功能(底层是cpu提供的功能)。
ReentrantLock除了用到了CAS之外,还用到了Unsafe的pack和unpack两个方法(LockSupport当中),除了性能更好之外,它可以精确的唤醒某一个线程。Demo代码位置
src/main/java/net/weichitech/juc/ReentrantLockTest.java · 小西学编程/java-learning - Gitee.com相关文章
JAVA并发之AtomicInteger原理分析
我今天看到的10张好照片(168)CayetanoGonzalez这是这一系列的第168次推送。逐相君每次会推送10张我今天看到的好照片。在你参观照片之前,我不愿意给照片加任何文字阐释。这可能会是一种干扰。废话不说
我今天看到的10张好照片(169)NathanLanders这是这一系列的第169次推送。逐相君每次会推送10张我今天看到的好照片。在你参观照片之前,我不愿意给照片加任何文字阐释。这可能会是一种干扰。废话不说,一起
我今天看到的10张好照片(175)EfeCaylak这是这一系列的第175次推送。逐相君每次会推送10张我今天看到的好照片。在你参观照片之前,我不愿意给照片加任何文字阐释。这可能会是一种干扰。废话不说,一起逐相Ef
我今天看到的10张好照片(160)LenaigChatel这是这一系列的第160次推送。逐相君每次会推送10张我今天看到的好照片。在你参观照片之前,我不愿意给照片加任何文字阐释。这可能会是一种干扰。废话不说,一起逐
我今天看到的10张好照片(171)LauraZalenga这是这一系列的第171次推送。逐相君每次会推送10张我今天看到的好照片。在你参观照片之前,我不愿意给照片加任何文字阐释。这可能会是一种干扰。废话不说,一起逐
人愿意做一件事有时不为挣钱,是因为只有这样做才对文图逐相君今天是我跟随曙光救援队来到河南灾区的第15天。两年了,我无论如何想不到,自己重回河南老家,居然是因为郑州下了一场特大暴雨。我更想不到,如今又因郑州疫情的爆发而进退两难。两
互认证杉岩存储系统与宝德自强服务器完成兼容性互认证测试近日,宝德自强系列服务器与杉岩分布式存储系统正式完成互认证测试。测试结果显示,双方产品兼容性良好整体运行流畅且性能表现优异,可满足用户安全性可靠性及关键性应用需求。随着云计算大数据
入驻上海鲲鹏生态创新中心宝德风采展示当前,随着5GIoT智能终端自动驾驶等发展,行业应用越来越多样化,端云协同智能边缘海量数据处理的场景也越来越多应用多样性也带来数据的多样性,数字文本图片视频图像结构数据非结构数据等
赞!宝德AI加速计算服务器PR4908R又揽一奖在21世纪的时间轴上,2020必将是载入史册的一年。伴随着人工智能云计算和5G为代表的新一轮科技革命进程,社会正在加速朝着智能化网联化和共享化的方向迈进。在这风云变幻的这一年中,宝
我今天看到的10张好照片(188)DavidStewart这是这一系列的第188次推送。逐相君每次会推送10张我今天看到的好照片。在你参观照片之前,我不愿意给照片加任何文字阐释。这可能会是一种干扰。废话不说,一起逐
我今天看到的10张好照片(190)这是这一系列的第190次推送。逐相君每次会推送10张我今天看到的好照片。在你参观照片之前,我不愿意给照片加任何文字阐释。这可能会是一种干扰。废话不说,一起逐相VassilisVas
为什么专升本那么难,我的经历希望可以给你一些帮助选择专升本是我人生至此为数不多且十分重要的选择,同大多数专升本的同学一样,我从最开始的信心满满,意气风发到后来的茫然无措,漫漫前路不知何处归途,到最后的破釜沉舟背水一战,每一次情感
为什么自律那么难,我的经历总结希望对你有所帮助懒惰拖延不自律,主要有4个原因外界太多的诱惑压力带来的恶性循环对未来形势的乐观估计挫败感带来的心理负担改变懒散拖延而变得自律,这里给你5点建议1要学会拒绝诱惑大部分人的意志力都是薄
企业对于网络危机公关处理,有哪些方式大家好,我是只为用户提升品牌价值的时代达信互联网发展,现在很多信息都可以通过媒体平台查看,像我们平时经常看到一些热点信息,这些信息当中有艺人企业,信息包含好与不好,对于一家上市企业
换手率的背后股票买卖就有成交额,成交额是股价成交量的积。日换手率是日成交股数与总股本的占比。通过换手率要知道资金的意图。换手率在35之间,一般是主力正在吸筹建仓,这时股价在低位,可以加自选关注
看起来像个笑话看到一个人的卖房与买房的事。如下2015年,我忙卖了唯一一套房子,面积108平米,单价8900,拿到手96万。买家是个外地人,买了房暂时不住,要出租。我们卖了房要再租房子,所以直接
我的爱国情怀七十年代初,出生在北方农村,七八岁时候放的电影都是战争题材,喜欢军装喜欢枪械,梦想着长大参军成为一名军人,好威风!上了中学,学习历史知道,尤其近代,我国饱受列强欺凌,割地赔款,人民
硬核ampampquot霸座ampampquot惹不起,全凭GPDP2Max,站着也能把方案改完今天,一条女子带着2个孩子躺在硬座上睡觉,遭到旅客质疑霸座。该女子不仅不让座,还甩手亮出6张车票的新闻刷了屏。看了这条新闻,很无奈,很佩服人家的神操作和钱包。广大网民的意见,总结了
贝壳王子无线蓝牙耳机体验平民价格王子享受自从苹果发售了AirPods后,无线蓝牙耳机俨然成了一个行业,甭管是声学产品老厂商还是耳机界的新兵,都纷纷推出自己的TWS蓝牙耳机,这些耳机价格有高有低。今天,就跟大家分享一款属于
闭关15天整理的linux文本处理和正则表达式学习资料闭关了15天,终于把文本处理工具和正则表达式的内容整理完了,今天分享给大家。本文将详细讲解1文本编辑工具之神VIM2各种文本工具3基本正则表达式和扩展正则表达式4文本处理三剑客之g
手把手教你企业级调度器LVS,带你实现负载均衡简单来说集群就是一群服务器合作做同一件事。随着互联网用户量越来越大,业务计算越来越复杂,对服务器的要求也越来越高。单台服务器很难单独去应对大量的需求,所以就需要通过集群,去实现服务
看完这篇,还学不会Nginx,我倒立洗头!(建议收藏)Nginx是一款轻量级的Web服务器反向代理服务器,由于其稳定性丰富的模块库灵活的配置和低系统资源的消耗而得到国内外互联网公司的一致认可。Nginx可以做web服务负载均衡(反向代