synchronized和ReentrantLock分析

参考:
子路老师博客:https://blog.csdn.net/java_lyvee/article/details/110996764
并发编程网:http://ifeve.com/monitors-java-synchronization-mechanism/

代码需求:

有一个猫窝
有猫长老、猫爸、 猫妈妈,还有6只小猫
猫爸爸需要进猫窝拿钱才捕猎,不然就睡觉
猫妈妈需要进猫窝拿扫把才做家务,不然也是睡觉
6只小猫就也想等着进猫窝玩
但是猫窝的进出权限是猫长老控制的

思路:

所以需要有4类线程,分别竞争一个猫窝,用syhchronized来实现。

代码:

⚠️注意:wait条件的时候要用while去判断,不能用if。

@Slf4j(topic = "console")
public class LockTest {static boolean isMoney = false;static boolean isClean = false;public static void main(String[] args) throws InterruptedException {// 需要while(条件) + wait()Object home = new Object();new Thread(() -> {synchronized (home) {while (!isMoney) {log.debug("sleeping");try {home.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("hunting");}}, "catDad").start();new Thread(() -> {synchronized (home) {while (!isClean) {log.debug("sleeping");try {home.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("cleaning");}}, "catMom").start();//为了能让dad和mon先调度TimeUnit.MILLISECONDS.sleep(10);for (int i = 0; i < 6; i++) {new Thread(() -> {synchronized (home) {log.debug("playing!!!");}}, "littleKitty"+ i).start();}new Thread(() -> {synchronized (home){isMoney = true;isClean = true;/*notifyAll只能唤醒所有线程,可能会导致唤醒不应该的线程。ReentrantLock 的 Condition可以解决这个问题。*/home.notifyAll();}},"catElder").start();}
}

缺点:

使用synchronized的线程唤醒notify只能叫醒一个线程,而notifyAll能叫醒该锁的所有线程,假如这时有3个条件的,但我们只要叫醒2个,就会有一个虚假唤醒了。java的AQS提供了ReentrantLockCondition,能很好解决。

ReentrantLock、Condition的await和signal

思路

Q:为啥使用ReentrantLockCondition呢?

A:synchronized的唤醒和唤醒条件的问题,能通过为一个ReentrantLock对象创建多个Condition对象,而各个Condition对象可以分开唤醒各自的waiting线程;

ReentrantLock用法

ReentrantLock可以替换synhcronized关键字,一个是jvm提供的用C++实现的,一个java的实现的。

//创建重入锁对象
ReentrantLock lock = new ReentrantLock();
//创建Condition对象
Condition moneyCondition = lock.newCondition();new Thread(() -> {//上锁的方式。通常要和try搭配,然后把解锁写在finally里lock.lock();try {while (!isMoney) {log.debug("sleeping");//各自的条件对象调用await方法moneyCondition.await();}} catch (Exception e) {e.printStackTrace();} finally {//解锁通常写在finnaly,不然抛异常后锁会不释放。//synchronized是由jvm管理的,只要抛异常了,锁就自动释放了。lock.unlock();}log.debug("hunting");}, "catDad").start();

代码:

@Slf4j(topic = "console")
public class ReentrantLockTest {static boolean isMoney = false;static boolean isClean = false;public static void main(String[] args) throws InterruptedException {//创建一个重入锁ReentrantLock lock = new ReentrantLock();//为重入锁创建2个条件对象Condition moneyCondition = lock.newCondition();Condition cleanCondition = lock.newCondition();new Thread(() -> {lock.lock();try {while (!isMoney) {log.debug("sleeping");//各自的条件对象调用await方法moneyCondition.await();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}log.debug("hunting");}, "catDad").start();new Thread(() -> {lock.lock();try {while (!isClean) {log.debug("sleeping");cleanCondition.await();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}log.debug("cleaning");}, "catMom").start();TimeUnit.MILLISECONDS.sleep(10);for (int i = 0; i < 6; i++) {new Thread(() -> {lock.lock();try {log.debug("playing!!!");} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}, "littleKitty" + i).start();}new Thread(() -> {lock.lock();try {//可以为各自的的条件进行唤醒,//signal对应notify唤醒一个线程//signal对应notifyAll唤醒所有线程isMoney = true;moneyCondition.signalAll();isClean = true;cleanCondition.signalAll();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}, "catElder").start();}
}

synchronized分析

线程的6个状态

参考这2篇,国内那篇透彻讲解最好别看了

https://blog.csdn.net/Baisitao_/article/details/99766322

https://www.cnblogs.com/aspirant/p/8900276.html

然后我总结了一个图:

总体来说在java并发编程中wait和time-wait有2种,

  1. 一种是在进行了锁操作(synchroized):

    它从waiting/timed-waiting状态不会直接到runnable。会先进行blocked等待获取锁。

  2. 另外一种是没有进行锁操作的:

    它从waiting/timed-waiting状态直接到runnable。不用等待获取锁。

当线程竞争不到锁时阻塞时

通过上面介绍,这是线程是进入blocked阻塞状态。当线程进入blocked状态,会有什么操作呢?

对于synchronized,是jvm来管理的,在jvm中有一个EntryList(双向链表)管理了这些获取不到锁的线程,我们在从jvm的监视器(monitor)相关知识了解到了。[java监视器概念]

进入EntryList的线程的顺序和获得锁启动的顺序是相反的即:先入后出

可以通过下面代码验证。

代码:

public static void main(String[] args) throws InterruptedException {Object lock = new Object();ArrayList<Thread> threads = new ArrayList<>();//用一个list装5个线程,并都进行锁竞争for (int i = 0; i < 5; i++) {threads.add(new Thread(() -> {synchronized (lock) {log.debug("{} running", Thread.currentThread().getName());}try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}, "t" + (i + 1)));}
//mian线程上锁时启动依次启动调度5个线程synchronized (lock) {log.debug("线程启动顺序: ");for (Thread thread : threads) {log.debug("{}", thread.getName());thread.start();//为了能按顺序被调度,因为拿不到锁,所以进入EntryList的顺序是t1 - t5TimeUnit.MILLISECONDS.sleep(100);}log.debug("拿到锁的顺序:");}
}

输出:

14:59:28.372 [main] DEBUG console - 线程启动顺序:
14:59:28.374 [main] DEBUG console - t1
14:59:28.477 [main] DEBUG console - t2
14:59:28.583 [main] DEBUG console - t3
14:59:28.688 [main] DEBUG console - t4
14:59:28.793 [main] DEBUG console - t5
14:59:28.897 [main] DEBUG console - 拿到锁的顺序:
14:59:28.898 [t5] DEBUG console - t5 running
14:59:28.898 [t4] DEBUG console - t4 running
14:59:28.899 [t3] DEBUG console - t3 running
14:59:28.899 [t2] DEBUG console - t2 running
14:59:28.899 [t1] DEBUG console - t1 running

结果入我们说的那样,先阻塞的线程最后才获得锁。

当线程调用wait时

线程调用wait方法进入waiting状态,并释放了锁。再当调用了notify方法来唤醒时,会发生什么?

对于synchronized,线程会把waiting状态的线程放进WaitSet中;

当线程被唤醒时,线程这是肯定获取不到锁,会从WaitSet调度进到EntryList中等待获得锁。

代码:

@Slf4j(topic = "console")
public class WaitAnalyze {static boolean isMoney = false;public static void main(String[] args) throws InterruptedException {Object home = new Object();new Thread(() -> {synchronized (home) {while (!isMoney) {log.debug("sleeping");try {home.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("hunting");}}, "CatDad").start();//为了dad先调度TimeUnit.MILLISECONDS.sleep(10);synchronized (home) {//直接唤醒dad再去启动kitty们isMoney = true;home.notifyAll();for (int i = 0; i < 6; i++) {log.debug("kitty的启动顺序:{}",i+1);new Thread(() -> {synchronized (home) {log.debug("playing!!!");}}, "Kitty" + (i + 1)).start();//为了kitty的调度顺序和启动顺序一致TimeUnit.MILLISECONDS.sleep(100);}/*把dad唤醒。dad的状态:waiting -> blocking,从WaitSet转移到EntryList*/}}

输出:

15:01:09.187 [CatDad] DEBUG console - sleeping
15:01:09.199 [main] DEBUG console - kitty的启动顺序:1
15:01:09.305 [main] DEBUG console - kitty的启动顺序:2
15:01:09.410 [main] DEBUG console - kitty的启动顺序:3
15:01:09.516 [main] DEBUG console - kitty的启动顺序:4
15:01:09.621 [main] DEBUG console - kitty的启动顺序:5
15:01:09.726 [main] DEBUG console - kitty的启动顺序:6
15:01:09.830 [Kitty6] DEBUG console - playing!!!
15:01:09.830 [Kitty5] DEBUG console - playing!!!
15:01:09.830 [Kitty4] DEBUG console - playing!!!
15:01:09.830 [Kitty3] DEBUG console - playing!!!
15:01:09.830 [Kitty2] DEBUG console - playing!!!
15:01:09.830 [Kitty1] DEBUG console - playing!!!
15:01:09.831 [CatDad] DEBUG console - hunting

虽然CatDad是在Kitty们在启动调度之前被唤醒,但是CatDad却是最后一个获得锁的。

这就证明了CatDad不是立即唤醒并先获得锁的,而是需要进入EntryList等待获得锁,然后因为EntryList是FILO(先入后出)的,

CatDad就是最后获得锁的。

ReentrantLock分析

ReentrantLock是java中的API,JUC包里的。锁的使用和jvm提供synchronized相似。所以可以通过学习其源码去学习java中锁机制的原理。

这里我们主要阐述的ReentrantLock和synchronized区别是在线程获取不到锁,进入blocked状态,然后到获得锁这个线程的启动顺序的差别。

上面我们知道阻塞状态的线程在synchronized中是把放入过ObjectMonitor的EntryList里,然后顺序是FILO的。

但是ReentrantLock的都是的阻塞队列是由基于AQS实现公平锁(FairSync)和非公平锁(NoFairSync)的所维护的,他的顺序是FIFO的,AQS的release方法可以看到:

把线程节点取出队列:

    @ReservedStackAccesspublic final boolean release(int arg) {if (tryRelease(arg)) {//先取的是头部节点Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

当线程竞争不到锁时阻塞时

代码:

 */
@Slf4j(topic = "console")
@SuppressWarnings({"all"})
public class SyncAnalyze {public static void main(String[] args) throws InterruptedException {ReentrantLock reentrantLock = new ReentrantLock();Condition condition = reentrantLock.newCondition();threads = new ArrayList<>();for (int i = 0; i < 5; i++) {threads.add(new Thread(() -> {reentrantLock.lock();try {log.debug("{} running", Thread.currentThread().getName());TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();} finally {reentrantLock.unlock();}}, "t" + (i + 1)));}TimeUnit.MILLISECONDS.sleep(100);{reentrantLock.lock();log.debug("线程启动顺序: ");for (Thread thread : threads) {log.debug("{}", thread.getName());thread.start();//为了能按顺序被调度,因为拿不到锁,所以进入EntryList的顺序是t1 - t5TimeUnit.MILLISECONDS.sleep(100);}log.debug("拿到锁的顺序:");reentrantLock.unlock();}}
}

输出:

18:05:16.279 [main] DEBUG console - 线程启动顺序:
18:05:16.279 [main] DEBUG console - t1
18:05:16.382 [main] DEBUG console - t2
18:05:16.484 [main] DEBUG console - t3
18:05:16.589 [main] DEBUG console - t4
18:05:16.690 [main] DEBUG console - t5
18:05:16.795 [main] DEBUG console - 拿到锁的顺序:
18:05:16.796 [t1] DEBUG console - t1 running
18:05:16.808 [t2] DEBUG console - t2 running
18:05:16.821 [t3] DEBUG console - t3 running
18:05:16.834 [t4] DEBUG console - t4 running
18:05:16.846 [t5] DEBUG console - t5 running

可以看到是FIFO的。

当线程调用await时

ReentrantLock当中Condition可以实现和synchronized的wait一样的功能,并且可以支持多条件。

当线程调用await方法,对应synchronized的WaitSet,在这里是ConditionObjet的AQS队列,而ConditionObject是AQS的内部类。

对应源码:

/*** Implements interruptible condition wait.* <ol>* <li> If current thread is interrupted, throw InterruptedException.* <li> Save lock state returned by {@link #getState}.* <li> Invoke {@link #release} with saved state as argument,*      throwing IllegalMonitorStateException if it fails.* <li> Block until signalled or interrupted.* <li> Reacquire by invoking specialized version of*      {@link #acquire} with saved state as argument.* <li> If interrupted while blocked in step 4, throw InterruptedException.* </ol>*/
public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();Node node = addConditionWaiter();int savedState = fullyRelease(node);int interruptMode = 0;while (!isOnSyncQueue(node)) {LockSupport.park(this);if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}

代码:

@Slf4j(topic = "console")
@SuppressWarnings({"all"})
public class WaitAnalyze {static boolean isMoney = false;public static void main(String[] args) throws InterruptedException {ReentrantLock rHome = new ReentrantLock();Condition condition = rHome.newCondition();log.debug("使用ReentrantLock方式");new Thread(() -> {rHome.lock();while (!isMoney) {log.debug("sleeping");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}rHome.unlock();log.debug("hunting");}, "CatDad").start();//为了dad先调度TimeUnit.MILLISECONDS.sleep(10);{rHome.lock();isMoney = true;condition.signalAll();for (int i = 0; i < 6; i++) {log.debug("kitty的启动顺序:{}", i + 1);new Thread(() -> {rHome.lock();log.debug("playing!!!");rHome.unlock();}, "Kitty" + (i + 1)).start();//为了kitty的调度顺序和启动顺序一致TimeUnit.MILLISECONDS.sleep(100);}rHome.unlock();}}}

输出:

19:20:03.275 [main] DEBUG console - 使用ReentrantLock方式
19:20:03.277 [CatDad] DEBUG console - sleeping
19:20:03.289 [main] DEBUG console - kitty的启动顺序:1
19:20:03.393 [main] DEBUG console - kitty的启动顺序:2
19:20:03.498 [main] DEBUG console - kitty的启动顺序:3
19:20:03.604 [main] DEBUG console - kitty的启动顺序:4
19:20:03.708 [main] DEBUG console - kitty的启动顺序:5
19:20:03.813 [main] DEBUG console - kitty的启动顺序:6
19:20:03.918 [CatDad] DEBUG console - hunting
19:20:03.918 [Kitty1] DEBUG console - playing!!!
19:20:03.919 [Kitty2] DEBUG console - playing!!!
19:20:03.919 [Kitty3] DEBUG console - playing!!!
19:20:03.919 [Kitty4] DEBUG console - playing!!!
19:20:03.919 [Kitty5] DEBUG console - playing!!!
19:20:03.919 [Kitty6] DEBUG console - playing!!!

和synchronized结果方式类似,只是因为阻塞队列的顺序原因,获得锁的顺序是FIFO的。

也可以证明在锁同步中,线程从wait到runnable状态会会先进入blocked状态等待获取锁。

【Java】synchronized和ReentrantLock分析相关推荐

  1. Java—synchronized和ReentrantLock锁详解

    关注微信公众号:CodingTechWork,一起学习进步. 1 synchronized 1.1 synchronized介绍 synchronized机制提供了对每个对象相关的隐式监视器锁,并强制 ...

  2. Java中的ReentrantLock和synchronized两种锁定机制的对比

    原文:http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html 多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之 ...

  3. java 同步方式 lock_java的两种同步方式, Synchronized与ReentrantLock的区别

    java在编写多线程程序时,为了保证线程安全,需要对数据同步,经常用到两种同步方式就是Synchronized和重入锁ReentrantLock. 相似点: 这两种同步方式有很多相似之处,它们都是加锁 ...

  4. Java字节码角度分析:Synchronized ——提升硬实力11

    在前面的文章中,有详细地介绍java字节码相关的知识,有兴趣的可以提前了解一下. 1.Java字节码的一段旅行经历--提升硬实力1 2.Java字节码角度分析a++ --提升硬实力2 3.Java字节 ...

  5. Java锁示例– ReentrantLock

    Welcome to Java Lock example tutorial. Usually when working with multi-threaded environment, we use ...

  6. synchronized,ReentrantLock、ReentrantReadWriteLock和StampedLock

    java四种上锁方式原理及适用场景区分 synchronized(monitor).ReentantLock(AQS).AtomicLong(CAS).LongAdder(XADD) 针对代码块需要同 ...

  7. Java Synchronized 重量级锁原理深入剖析上(互斥篇)

    前言 线程并发系列文章: Java 线程基础 Java 线程状态 Java "优雅"地中断线程-实践篇 Java "优雅"地中断线程-原理篇 真正理解Java ...

  8. Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程

    前言 线程并发系列文章: Java 线程基础 Java 线程状态 Java "优雅"地中断线程-实践篇 Java "优雅"地中断线程-原理篇 真正理解Java ...

  9. 【并发编程】线程锁--Synchronized、ReentrantLock(可重入锁)

    在说锁之前,我们要明白为什么要加锁,不加锁会怎样? 在并发编程中,很容易出现线程安全问题,接下来我们看个很经典的例子--银行取钱,来看一下有关线程安全的问题. 取钱的流程可以分为一下几个步骤: 1.用 ...

最新文章

  1. Asp.net MVC调试-使用IP监听
  2. 【Servlet】HTTP 协议之请求方式、Servlet介绍、Servlet 的生命周期
  3. 如何关闭Windows10任务栏里的应用图标
  4. 雨课堂显示服务器无法连接,雨课堂用的什么云服务器
  5. 开发人员应该用好的一些网站
  6. Infographic Modern Graphs Mac(现代信息图表动画fcpx插件)
  7. Python之访问set
  8. 变量提升、作用域this实战(真题:看代码输出)
  9. 如何创建线程?如何实现Runnable接口?
  10. drool 7.x 语法和属性
  11. asp.net pdf如何转换成tif_PDF如何转换成PPT格式?PDF转PPT软件使用方法分享
  12. 【Linux】腾讯云服务器搭建环境
  13. 谈一谈今年的移动互联网寒冬
  14. 计算机关闭多重网络协议,Win7多重网络问题
  15. MTK6577 Android源代码目录
  16. 大学计算机课程教学建议,计算机应用基础课程教学的建议
  17. 使用Nginx Upstream 部署 OpenERP
  18. svg的学习笔记《一》:如何使用svg sprite
  19. 卫龙更新招股书:上半年净利润同比下降,产能未饱和仍要募资扩产
  20. 【Excel】Excel中实现中文转拼音(自定义函数方式)

热门文章

  1. css检测,CSS-验证
  2. 笔记本电脑怎么打不开计算机,笔记本电脑打不开了 按什么也没用 怎么处理
  3. 卷积云神经网络_用于卷积神经网络训练的地基云图数据库构建方法与流程
  4. 超详细解读带你读懂单细胞RNA测序分析的最佳实践教程 (原理、代码和评述)
  5. High-level
  6. Sivers Semiconductors 与 Richardson RFPD 签署全球分销协议
  7. 计算机核心是什么如何判断性能指标,如何看懂电脑CPU的性能参数?看完这篇就会了...
  8. 输出长方体的体积和表面积
  9. 费希尔DVC6200p数字式阀门控制器
  10. 科技新品 | 戴森限量版红金吹风机;环旭电子安卓系统移动型POS机;格之格首款智能便携式标签机...