• ReentrantLock通过Condition实现锁对象的监视器功能


    公平锁和非公平锁只有两处不同,总结:

    1、非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
    2、非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在tryAcquire方法中,如果发现锁这个时候被释放了(state == 0),
    非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有等待,则不去抢锁,乖乖排到后面。
    
    公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
    相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。
    当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二、Condition

    其中,Lock替代了 synchronized 方法和语句的使用,【Condition】 替代了 Object 【监视器】方法的使用

    我们以往开发,需要用到锁, 并且要用到睡眠和唤醒就只有synchronized, 但是【ReentrantLock】也实现了,Lock实现了锁,Condition还实现了睡眠和唤醒的功能。
    ConditionObject主要是为并发编程中的同步提供了等待通知的实现方式,可以在不满足某个条件的时候挂起线程等待。直到满足某个条件的时候在唤醒线程。

    synchronized的缺点

    1synchronizednotify() 方法一次只能唤醒一个线程,而且唤醒线程的方式是随机的,从处于等待集中随机选取一个线程唤醒。
    并且是唤醒等待队列中优先级最高的线程。
    2notify()可能会导致死锁,原因是notify唤醒了线程A,可能要获取的锁此时已经被别的线程获取了,那么线程A一直在阻塞,
    而线程A获得锁,这个锁对应的阻塞队列里面的其他线程永远得不到执行。
    
    正确的使用场景是WaitSet中等待的条件是相同, 确保唤醒任意一个都能够执行后面的事项。 
    如果被唤醒的线程无法正确处理,务必继续notify()下一个线程,并且自身要回到WaitSet等待集中。
    
    那就要用到下面2个方法,他们可以控制具体线程的唤醒和阻塞,但是不释放锁。
    LockSupport.park(this) 阻塞,但是不释放锁。
    LockSupport.unpark(thread对象)  唤醒指定的线程。
    与之对应的还有下面的2个方法
    Thread.sleep(n) 不会释放占有的锁,但是会让出cpu执行权,到时自动唤醒,然后抢cpu的执行权。
    Object.wait(n) 会释放占有的锁,没传时间就一直睡眠,需要其他线程调用Object对象notify方法。加了时间自动醒,进到阻塞队列再来重新获取锁。
    
    
    object.wait和thread.wait区别
    每个类都继承了Object类,Object有个非静态的方法wait,所以要调用wait必须先有实例对象,才能调用wait方法。
    Thread类也继承了Object类。
    只有object.wait这么调用的,没有见过thread.wait,因为调用wait方法,必须要持有这个对象的锁,不然会报错,我们要拿到一个对象的锁,
    就要使用synchronized来锁住对象。 我们也不会把线程对象thread当做我们的并发锁,
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    三、synchronized加锁原理

    每个对象都关联了一个监视器,线程可以对其进行【加锁和解锁】操作。在同一时间,只有一个线程可以拿到对象上的监视器锁。
    如果其他线程在锁被占用期间试图去获取锁,那么将会【被阻塞】直到成功获取到锁。
    同时,【监视器锁可以重入】,也就是说如果线程 T 拿到了锁,那么线程T可以在解锁之前重复获取锁;每次解锁操作会反转一次加锁产生的效果
    
    
    synchronized 代码块。synchronized(object) 在对某个对象上执行加锁时,会尝试在该对象的监视器上进行加锁操作,
    只有成功获取锁之后,线程才会继续往下执行。线程获取到了监视器锁后,将继续执行 synchronized 代码块中的代码,如果代码块执行完成,
    或者抛出了异常,线程将会自动对该对象上的【监视器执行解锁】操作。
    
    synchronized 作用于方法,称为同步方法。同步方法被调用时,会自动执行加锁操作,只有加锁成功,方法体才会得到执行。
    如果被 synchronized 修饰的方法是实例方法,那么这个实例的监视器会被锁定。
    如果是 static 方法,线程会锁住相应的 Class 对象的监视器。
    方法体执行完成或者异常退出后,会自动执行解锁操作。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    小知识点:对 【Class】加锁、对【对象】加锁,它们之间不构成同步。synchronized 作用于静态方法时是对 Class 对象加锁,作用于实例方法时是对实例加锁。
    面试中经常会问到一个类中的两个 synchronized static 方法之间是否构成同步?构成同步。

    【等待状态】是指进程由于某种条件不具备,在等待队列中排队,等待cpu对它进行调度。
    【阻塞状态】可能是由于竞争共享资源的情况下,没有得到锁或信号量的条件不满足而在临界区之外被暂停执行了。

    文章地址

    Condition使用案例

    CountDownLatch主要功能,是一个计数器,主要countDown()wait()方法
    
    1、一开始创建CountDownLatch对象,需要指定一个数字,这个数字和公平锁里面的state一样,比如100,就表示某个锁被重入了100次,
    所以需要释放一百次,countDown就类似于释放一次锁的意思,所以countDown方法,要放在子线程里面执行,子线程完成一次任务,就减一。
    
    2、什么场景要调用wait()呢,比如启动服务,需要启动创建N个组件,创建了一个组件就countDown(),我们只需要在线程池外面wait就可以了,
    就等status变成0就可以了,如果没有变成0,那么调用wait的线程就会【阻塞】,并且进到阻塞队列,为什么会进到阻塞队列,
    因为假如这个CountDownLatch对象是多个线程共享,那么多个线程都想知道status什么时候变成0,所以只要是通过CountDownLatch调用wait那
    么就会阻塞,那么多个线程阻塞总得排队,多个线程调用wait都有可能阻塞进到队列,那么什么时候唤醒这些阻塞队列,就是等state变成0,
    然后通过自旋的方式,从阻塞队列里面把所有Node取出来,挨个唤醒。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    自定义mvc的实现03
    java-php-python-ssm-电脑小白网站-计算机毕业设计
    Dynamics 365 Marketing自定义渠道的步骤
    机器学习高手之路:发现TensorFlow学习网站的无限可能!
    鲁大师7月新机性能/流畅榜:性能跑分突破123万!
    AtCoder Beginner Contest 320 A/B/D
    常用的OLED透明显示屏款式,有几种?
    ansible执行用户问题
    python协程详细解释以及例子
    走进音视频的世界——mp3封装格式
  • 原文地址:https://blog.csdn.net/weixin_37862824/article/details/134431189