synchronized与Lock
一、概念
synchronized是java中的一个关键字,表明一段代码需要同步执行。
Lock是一个接口,其有多个实现类,像我们比较常用的ReentrantLock。
二、实现
synchronized是托管给jvm执行的,使用起来比较简洁。
Lock是jdk代码实现的,需要开发者自己管理的细节比较多。
三、ReentrantLock中的公平锁和非公平锁
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
由ReentrantLock的源码可以看到初始化时默认是非公平锁,要想使用公平锁需要使用带参数的构造函数进行初始化。
以下对于公平锁和非公平锁的内容来自《石杉的架构笔记》-https://juejin.im/post/5c091259f265da61171c86b4 ,看后受益匪浅,感谢
非公平锁
注意一点,此时线程2可还停留在等待队列里啊,还没开始尝试重新加锁呢!
然而,不幸的事情发生了,这时半路杀出个程咬金,来了一个线程3!线程3突然尝试对ReentrantLock发起加锁操作,此时会发生什么事情?
很简单!线程2还没来得及重新尝试加锁呢。也就是说,还没来得及尝试重新执行CAS操作将state的值从0变为1呢!线程3冲上来直接一个CAS操作,尝试将state的值从0变为1,结果还成功了!
一旦CAS操作成功,线程3就会将“加锁线程”这个变量设置为他自己。给大家来一张图,看看这整个过程:
这就导致线程2被唤醒过后,重新尝试加锁执行CAS操作,结果毫无疑问,失败!
原因很简单啊!因为加锁CAS操作,是要尝试将state从0变为1,结果此时state已经是1了,所以CAS操作一定会失败!
一旦加锁失败,就会导致线程2继续留在等待队列里不断的等着,等着线程3释放锁之后,再来唤醒自己,真是可怜!先来的线程2居然加不到锁!
同样给大家来一张图,体会一下线程2这无助的过程:
如果你用默认的构造函数来创建ReentrantLock对象,默认的锁策略就是非公平的。
在非公平锁策略之下,不一定说先来排队的线程就就先会得到机会加锁,而是出现各种线程随意抢占的情况。
公平锁
如果是公平锁的策略,那么此时线程3不会跟个愣头青一样盲目的直接加锁。
他会先判断一下:咦?AQS的等待队列里,有没有人在排队啊?如果有人在排队的话,说明我前面有兄弟正想要加锁啊!
如果AQS的队列里真的有线程排着队,那我线程3就不能跟个二愣子一样直接抢占加锁了。
因为现在咱们是公平策略,得按照先来后到的顺序依次排队,谁先入队,谁就先从队列里出来加锁!
所以,线程3此时一判断,发现队列里有人排队,自己就会乖乖的排到队列后面去,而不会贸然加锁!
同样,整个过程我们用下面这张图给大家直观的展示一下:
接着,线程2不是被唤醒了么?他就会重新尝试进行CAS加锁,此时没人跟他抢,他当然可以加锁成功了。
然后呢,线程2就会将state值变为1,同时设置“加锁线程”是自己。最后,线程2自己从等待队列里出队。
整个过程,参见下图:
这个就是公平锁的策略,过来加锁的线程全部是按照先来后到的顺序,依次进入等待队列中排队的,不会盲目的胡乱抢占加锁,非常的公平。