synchronizedphp
Reentrantlockhtml
ReentrantReadWriteLockjava
为何须要锁?缓存
为何JAVA有了synchronize还须要Reentrantlock和ReentrantReadWriteLock?优化
synchronize和lock分别怎么实现同步快(原子性,一致性,禁重排序)?ui
synchronize和lock分别怎么实现锁的优化,可重入锁,偏向锁?atom
lock如何实现公平锁(synchronize是非公平锁)?spa
目的:.net
锁的目的是防止资源的竞争,主要从 原子性(一致性),可见性,防止处理重排序 三个方面来处理, volatile知足了后面两个特性,JAVA从两方面来实现锁线程
synchronized与ReentrantLock ,使用上看区别
1, synchronize在获取锁阻塞的时候是不能打断的
2, synchronize无超时机制,阻塞了的话只能一直阻塞形成死锁
3,synchronize只能notify,wait,若是须要两个或以上条件就不能用了,如: JAVA阻塞队列的实现,须要用是否为空和是否已满两个条件来阻塞线程
看lock相关的API就知道, 主要就是解决这几个问题
方法名称 | 描述 |
lock | 获取锁,若是锁没法获取,那么当前的线程就变为不可被调度,直到锁被获取到 |
lockInterruptibly | 获取锁,除非当前线程被中断。若是获取到了锁,那么当即返回,若是获取不到,那么当前线程变得不可被调度,一直休眠直到下面两件事情发生:一、当前线程获取到了锁
二、其余的线程中断了当前的线程 |
tryLock | 若是调用的时候可以获取锁,那么就获取锁而且返回true,若是当前的锁没法获取到,那么这个方法会马上返回false |
tryLcok(long time,TimeUnit unit) | 在指定时间内尝试获取锁若是能够获取锁,那么获取锁而且返回true,若是当前的锁没法获取,那么当前的线程变得不可被调度,直到下面三件事之一发生:一、当前线程获取到了锁
二、当前线程被其余线程中断 三、指定的等待时间到了 |
unlock | 释放当前线程占用的锁 |
newCondition | 返回一个与当前的锁关联的条件变量。在使用这个条件变量以前,当前线程必须占用锁。调用Condition的await方法,会在等待以前原子地释放锁,并在等待被唤醒后原子的获取锁 |
读写锁用于读多写少的状况,即当一条线程获取写锁后,后面的读锁都被阻塞,等待获取写锁的线程完成释放。
场景,如本地缓存失效,当须要去DB拿数据进行写入的操做,须要阻塞其它读的操做.
固然,读写锁也是能够基于notifyAll和wait实现
须要注意的是
synchronized锁
实现依赖
原子性,可见性和重排序都是依靠指令。方法同步和代码块同步依靠Monitor指令,代码块同步是使用monitorenter和monitorexit指令实现
锁信息保存在JAVA对象头里,准确说是Mark Word
synchronize的阻塞,依靠几个队列,属于不公平锁(线程先CAS竞争锁,再进队列)
ContentionList(LIFO)-->EntryList(LIFO)-->OnDeck-->Owner-->Wait Set (http://www.cnblogs.com/lykm02/p/4516777.html )
锁的转换方面
无锁-->偏向锁-->轻量锁-->重量锁 (http://blog.csdn.net/xad707348125/article/details/47189107)
偏向锁和可重入锁的实现
可重入锁,即当本线程进入同一锁时能够进行屡次上锁,固然也须要屡次释放
偏向锁,即当获取线程再次进入同步块时不须要再次竞争(CAS),当某个Core CAS成功时必然会引发总线风暴,这就是所谓的本地延迟,本质上偏向锁就是为了消除CAS,下降Cache一致性流量
原理:
1, 锁保存当前锁的线程,判断同一个线程时容许
用一个计数器去记录当前重入的次数,当进入时计数器+1, 当释放锁时计算器-1, 当为0时表示可竞争
synchronized
实现方式, 会在 Mark Word 存储获取锁线程的ID,而后栈帧中也存储线程ID,之后该线程再次进入同步块(同步方法)时不须要花费CAS了。
lock
实现方式, 用JAVA代码实现处理,跟踪下 lock()(NonfairSyncCAS获取锁失败)->acquire(AQS尝试获取锁)-->tryAcquire(nonfairTryAcquire)-->nonfairTryAcquire(Sync 以下处理):
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //再次判断是不是未锁状态,state为0为未有线程获取锁 if (compareAndSetState(0, acquires)) { //CAS再次竞争获取锁,此处是公平锁与非公平锁的区别 setExclusiveOwnerThread(current); return true; } } //这里是是实现偏向锁的关键,比较若是是当前锁就不进入CLH队列后面的竞争了 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
synchronize自己是非公平锁,无公平性实现.
lock非公平锁代码(详细如上)
if (compareAndSetState(0, acquires)) { //如锁被释放,是非公平锁的话,用CAS再次竞争获取锁
公平锁此段代码以下:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {//如锁被释放,必须当队列为空时才去CAS竞争锁 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
多个线程一块儿用CAS去尝试获取一个共同的可见性(volatile)的变量,获取成功即为获取锁
如 全部线程都运行
/** * 自旋锁方式去实现阻塞 * * 缺点:没法实现公平性,若是大量使用会增长CPU的Cache一致性流量开销 */ public static void CASLock() { // 不断去获取CAS的锁,如成功表示获取锁成功 while (state.compareAndSet(0, 1)) { } } public static void CASUnlock() { if (!state.compareAndSet(1, 0)) { // 释放锁异常 throw new RuntimeException(); } }
import java.util.concurrent.atomic.AtomicInteger; public class TicketLock { private AtomicInteger serviceNum = new AtomicInteger(); // 服务号 private AtomicInteger ticketNum = new AtomicInteger(); // 排队号 public int lock() { // 首先原子性地得到一个排队号 int myTicketNum = ticketNum.getAndIncrement(); // 只要当前服务号不是本身的就不断轮询 while (serviceNum.get() != myTicketNum) { } return myTicketNum; } public void unlock(int myTicket) { // 只有当前线程拥有者才能释放锁 int next = myTicket + 1; serviceNum.compareAndSet(myTicket, next); } }
CLH是在前驱节点的属性上自旋,
组成一个队列后,每一个节点都有保存当前节点获取锁的状态,和前一个节点的指向,获取锁的步骤
1, 新加个节点,并把节点经过自旋指向tail节点
2, 成功后,不停判断指向节点的锁状态,当前节点锁释放时获取锁
3, 释放锁,改变自身的锁持有状态就行
java 就是运用的CLH锁,但有所改进,主要包括
1, 指向了head
2, 在等待机制上由原来的自旋改为阻塞唤醒
详细介绍: http://blog.csdn.net/chenssy/article/details/50432195
而MCS是在本地属性变量上自旋。
资料参考: http://coderbee.net/index.php/concurrent/20131115/577/comment-page-1
欢迎关注个人公众号, 一块儿来构建咱们的知识体系