以前只是对Java各类锁都有所认识,但没有一个统一的整理及总结,且没有对“锁升级”这一律念的加深理解,今天趁着周末好好整理下以前记过的笔记,并概括为此博文,主要参考资源为《Java并发编程的艺术》与《Java多线程编程核心技术》,有须要的朋友能够私信评论我,这个是有书签的PDF电子版!算法
平时你们都知道的锁通常都有:CAS锁,synchronized锁,ReentranLock锁等,可是并无了解各自的用处与一些细节,这里用XMind画一个图,并作一个简单的总结。 数据库
乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不一样角度。在Java和数据库中都有此概念对应的实际应用。编程
先说概念。对于同一个数据的并发操做:多线程
根据从上面的概念描述咱们能够发现:并发
悲观锁适合写操做多的场景,先加锁能够保证写操做时数据正确。源码分析
乐观锁适合读操做多的场景,不加锁的特色可以使其读操做的性能大幅提高。性能
代码举例:ReentranLock中采用lock()与unlock方法锁住同步资源块测试
注意事项:ui
正确的写法:一开始就ock()方法,紧跟着try...finally,unlock()方法必定要在finally{}中的第一行。spa
错误的写法:lock没有紧跟着try...语句;没有一开始就lock()方法锁住资源
阻塞或唤醒一个Java线程须要操做系统切换CPU状态来完成,这种状态转换须要耗费处理器时间。若是同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。
在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。若是物理机器有多个处理器,可以让两个或以上的线程同时并行执行,咱们就可让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。而为了让当前线程“稍等一下”,咱们需让当前线程进行自旋,若是在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就能够没必要阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。
自旋锁的缺点:
从概念上来看,就知道自旋锁自己是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。若是锁被占用的时间很短,自旋等待的效果就会很是好。反之,若是锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。因此,自旋等待的时间必需要有必定的限度,若是自旋超过了限定次数(默认是10次,可使用-XX:PreBlockSpin来更改)没有成功得到锁,就应当挂起线程。简单来讲就是自旋的次数跟时间超过必定的阈值就可能浪费处理器的资源。
自旋锁的CAS实现:
在AtomicInteger源码中就使用了CAS思想(实际上就是调用unsafe中方法),采用do-while循环(这是一个CAS经常使用的do{}while(){},还有就是for(;;){if(...) return}),这里就是一个CAS操做,首先do{...}读取值,以后在经过循环while中CAS自旋修改值,直到成功为止。
自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK 6中变为默认开启,而且引入了自适应的自旋锁(适应性自旋锁)。
自适应意味着自旋的时间(次数)再也不固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
若是在同一个锁对象上,自旋等待刚刚成功得到过锁,而且持有锁的线程正在运行中,那么虚拟机就会认为此次自旋也是颇有可能再次成功,进而它将容许自旋等待持续相对更长的时间。
若是对于某个锁,自旋不多成功得到过,那在之后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源,即根据实际状况,决定自旋的时间。
公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能得到锁(排队获取锁)。
非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但若是此时锁恰好可用,那么这个线程能够无需阻塞直接获取到锁,因此非公平锁有可能出现后申请锁的线程先获取锁的场景(尝试获取锁,不行就从新进队尾等待)。
咱们先结合ReentranLock中的源码结构及部分源码分析下,能够获得如下两点:
(1)实际上,ReentranceLock中有一个内部类Sync,ReentranceLock添加锁/释放锁等关键操做都是由它完成的,而且它继承了AQS(AbstractQueuedSynchronizer,这是一个很重要的能学到不少知识的须要好好分析源码的类,以后会抽时间好好分析),源码注释有有这么一句话:Synchronizer providing all implementation mechanics。
(2)ReentrantLock它还有公平锁FairSync和非公平锁NonfairSync两个子类,ReentrantLock默认使用非公平锁,也能够经过构造器来显示的指定使用公平锁。
接下来咱们分别看公平锁与非公平锁加锁的实现对比:
公平锁加锁方法: 非公平锁加锁方法:
咱们能够清晰的看出有一个公平锁中有一个hasQueuePredecessors()方法:判断当前线程是不是队头,不是的话不会去处理。这也就是公平锁与非公平锁最大的区别。
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会由于以前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优势是可必定程度避免死锁 。
关键字synchronized在使用时,当一个线程获得一个对象锁后,再次请求对象锁时是能够再次获得该对象锁的,这也证实synchronized块/方法的内部调用本类的其它synchronized方法/块时,是能够永远获得锁的。
编写测试类:
运行结果:
service1()
service2()
service3()
从结果上来讲,“可重入锁”概念就是:本身可以再次获取本身的内部锁,同时当存在子类继承关系时,子类也彻底能够经过“可重入锁”调用父类的同步方法的。
好了,以上都是经过synchronized关键字的举例,接下来咱们一样采用对比的方法对比ReentranLock部分关键源码来讲明可重入锁与非可重入锁细节。实际上ReentranLock是没有非可重入锁的实现的,那么咱们能够类比就行。
ReentrantLock继承父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。当线程尝试获取锁时:
可重入锁:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // state为0锁处于空闲状态 if (compareAndSetState(0, acquires)) { // 获取成功以后,当前线程是该锁的持有者 setExclusiveOwnerThread(current); return true; } } // 锁不是空闲状态,可是当前线程是该锁的持有者的话,实现可重入 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; // state+1 可重入数 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; // 返回true,表示获取锁成功(可重入的) } return false; }
非可重入锁:
/** * 相似可重入操做类比出非可重入操做 * @param acquires * @return */ final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 非重入锁直接尝试获取该锁 if (compareAndSetState(0, acquires)) { // 这里acquires实际就是1,对state+1 // 获取以后设置为持有者,返回true,表示成功,其它状况都是false,即不能被重入了 setExclusiveOwnerThread(current); return true; } // 锁不是空闲状态,可是当前线程是该锁的持有者的话,实现可重入 else if { return false; } }
释放锁时,一样都是线程先尝试获取当前status的值,并判断当前线程是否是持有锁的线程的前提下
可重入锁:
protected final boolean tryRelease(int releases) { int c = getState() - releases; // 保证释放锁的必须是当前线程 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 释放后state为0,则持有者置为null if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 不然设置重置后的state setState(c); return free; }
非可重入锁:
/** * 相似可重入锁释放锁操做 获得非可重入锁操做 * @param releases * @return */ protected final boolean tryRelease(int releases) { // 保证释放锁的必须是当前线程 if (Thread.currentThread() != getExclusiveOwnerThread()) { throw new IllegalMonitorStateException(); } else { // 非可重入锁释放锁直接将持有者置为null setExclusiveOwnerThread(null); // state直接置为0 setState(0); return true; } }
----------------------------未完待续,下一个继续介绍共享锁与排它锁(结合部分源码),同时重点介绍锁升级-----------------------------------------------