公平锁和非公平锁只有两处不同,总结:
java
1、非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
2、非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在tryAcquire方法中,如果发现锁这个时候被释放了(state == 0),
非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有等待,则不去抢锁,乖乖排到后面。
公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。
当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
二、Condition
其中,Lock替代了 synchronized 方法和语句的使用,【Condition】 替代了 Object 【监视器】方法的使用
我们以往开发,需要用到锁, 并且要用到睡眠和唤醒就只有synchronized, 但是【ReentrantLock】也实现了,Lock实现了锁,Condition还实现了睡眠和唤醒的功能。
ConditionObject主要是为并发编程中的同步提供了等待通知的实现方式,可以在不满足某个条件的时候挂起线程等待。直到满足某个条件的时候在唤醒线程。
synchronized的缺点
java
1、synchronized的 notify() 方法一次只能唤醒一个线程,而且唤醒线程的方式是随机的,从处于等待集中随机选取一个线程唤醒。
并且是唤醒等待队列中优先级最高的线程。
2、notify()可能会导致死锁,原因是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当做我们的并发锁,
三、synchronized加锁原理
java
每个对象都关联了一个监视器,线程可以对其进行【加锁和解锁】操作。在同一时间,只有一个线程可以拿到对象上的监视器锁。
如果其他线程在锁被占用期间试图去获取锁,那么将会【被阻塞】直到成功获取到锁。
同时,【监视器锁可以重入】,也就是说如果线程 T 拿到了锁,那么线程T可以在解锁之前重复获取锁;每次解锁操作会反转一次加锁产生的效果
synchronized 代码块。synchronized(object) 在对某个对象上执行加锁时,会尝试在该对象的监视器上进行加锁操作,
只有成功获取锁之后,线程才会继续往下执行。线程获取到了监视器锁后,将继续执行 synchronized 代码块中的代码,如果代码块执行完成,
或者抛出了异常,线程将会自动对该对象上的【监视器执行解锁】操作。
synchronized 作用于方法,称为同步方法。同步方法被调用时,会自动执行加锁操作,只有加锁成功,方法体才会得到执行。
如果被 synchronized 修饰的方法是实例方法,那么这个实例的监视器会被锁定。
如果是 static 方法,线程会锁住相应的 Class 对象的监视器。
方法体执行完成或者异常退出后,会自动执行解锁操作。
小知识点:对 【Class】加锁、对【对象】加锁,它们之间不构成同步。synchronized 作用于静态方法时是对 Class 对象加锁,作用于实例方法时是对实例加锁。
面试中经常会问到一个类中的两个 synchronized static 方法之间是否构成同步?构成同步。
【等待状态】是指进程由于某种条件不具备,在等待队列中排队,等待cpu对它进行调度。
【阻塞状态】可能是由于竞争共享资源的情况下,没有得到锁或信号量的条件不满足而在临界区之外被暂停执行了。
Condition使用案例
java
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取出来,挨个唤醒。