①. 乐观锁和悲观锁
- ①. 悲观锁(synchronized关键字和Lock的实现类都是悲观锁)
- 什么是悲观锁?认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改
- 适合写操作多的场景,先加锁可以保证写操作时数据正确(写操作包括增删改)、显式的锁定之后再操作同步资源
- synchronized关键字和Lock的实现类都是悲观锁
- ②. 乐观锁
- 概念:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
- 乐观锁在Java中通过使用无锁编程来实现,最常采用的时CAS算法,Java原子类中的递增操作就通过CAS自旋实现的
- 适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升
- 乐观锁一般有两种实现方式(采用版本号机制、CAS算法实现)
③. 伪代码
//悲观锁的调用方式 public synchronized void m1(){ //加锁后的业务逻辑 } //保证多个线程使用的是同一个lock对象的前提下 ReetrantLock lock=new ReentrantLock(); public void m2(){ lock.lock(); try{ //操作同步资源 }finally{ lock.unlock(); } } //乐观锁的调用方式 //保证多个线程使用的是同一个AtomicInteger private AtomicInteger atomicIntege=new AtomicInteger(); atomicIntege.incrementAndGet();
②. 公平锁和非公平锁
- ①. 什么是公平锁和非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁类似排队打饭先来后到
非公平锁:是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象
注意:synchronized 和 ReentrantLock 默认是非公平锁
②. 排队抢票案例(公平出现锁饥饿)
锁饥饿:我们使用5个线程买100张票,使用ReentrantLock默认是非公平锁,获取到的结果可能都是A线程在出售这100张票,会导致B、C、D、E线程发生锁饥饿(使用公平锁会有什么问题)
class Ticket { private int number = 50; private Lock lock = new ReentrantLock(true); //默认用的是非公平锁,分配的平均一点,=--》公平一点 public void sale() { lock.lock(); try { if(number > 0) { System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number); } }finally { lock.unlock(); } } /*Object objectLock = new Object(); public void sale(){ synchronized (objectLock) { if(number > 0) { System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number); } } }*/ } public class SaleTicketDemo { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"a").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"b").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"c").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"d").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"e").start(); } }
③. 源码解读(ReentrantLock默认是非公平锁)
- 公平锁:排序排队公平锁,就是判断同步队列是否还有先驱节点的存在(我前面还有人吗?),如果没有先驱节点才能获锁
- 先占先得非公平锁,是不管这个事的,只要能抢获到同步状态就可以
- ReentrantLock默认是非公平锁,公平锁要多一个方法,所以非公平锁的性能更好(aqs源码)
④. 为什么会有公平锁、非公平锁的设计?为什么默认非公平?面试题
恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间存在的还是很明显的,所以非公平锁能更充分的利用CPU的时间片,尽量减少CPU空闲状态时间
使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大了,所以就减少了线程的开销线程的开销
⑤. 什么时候用公平?什么时候用非公平?面试题
(如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了。否则那就用公平锁,大家公平使用)