1.1什么是锁?html
在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。java
锁一般须要硬件支持才能有效实施。这种支持一般采起一个或多个原子指令的形式,如"test-and-set", "fetch-and-add" or "compare-and-swap"”。这些指令容许单个进程测试锁是否空闲,若是空闲,则经过单个原子操做获取锁。redis
1.2.锁的一个重要属性 粒度数据库
在引入锁粒度以前,须要了解关于锁的三个概念:缓存
一、锁开销 lock overhead 锁占用内存空间、 cpu初始化和销毁锁、获取和释放锁的时间。程序使用的锁越多,相应的锁开销越大多线程
二、锁竞争 lock contention 一个进程或线程试图获取另外一个进程或线程持有的锁,就会发生锁竞争。锁粒度越小,发生锁竞争的可能性就越小并发
三、死锁 deadlock 至少两个任务中的每个都等待另外一个任务持有的锁的状况锁粒度是衡量锁保护的数据量大小,一般选择粗粒度的锁(锁的数量少,每一个锁保护大量的数据),在当单进程访问受保护的数据时锁开销小,可是当多个进程同时访问时性能不好。由于增大了锁的竞争。相反,使用细粒度的锁(锁数量多,每一个锁保护少许的数据)增长了锁的开销可是减小了锁竞争。例如数据库中,锁的粒度有表锁、页锁、行锁、字段锁、字段的一部分锁分布式
相关术语 Critical Section(临界区)、 Mutex/mutual exclusion(互斥体)、 Semaphore/binary semaphore(信号量)memcached
2.锁的种类函数
2.1.独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。 (ReentrantLock、 Synchronized)
共享锁是指该锁可被多个线程所持有。 (ReadWriteLock)
互斥锁/读写锁
独享锁/共享锁这是广义上的说法,互斥锁/读写锁就分别对应具体的实现。在Java中如ReentrantLock就是互斥锁(独享锁), ReadWriteLock就是读写锁( 读锁是共享锁,写锁是独享锁)。 独享锁与共享锁也是经过AQS来实现的。
锁升级:读锁到写锁 (不支持)
锁降级:写锁到读锁 (支持)
2.2.读写锁 ReentrantReadWriteLock
高16位表明写锁,低16位表明读锁
2.2.公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并非按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能会形成饥饿现象。
对于Java ReentrantLock和而言,经过构造函数指定该锁是不是公平锁,默认是非公平锁。非公平锁的优势在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。因为其并不像ReentrantLock是经过AQS的控制线程对锁的获取, 因此并无任何办法使其变成公平锁。
2.3.可重入锁
可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
ReentrantLock、ReadWriteLock和Synchronized都是可重入锁。可重入锁的一个好处是可必定程度避免死锁
如上面的代码,若是synchronized不是可重入锁的话,testB就不会被当前线程执行,从而造成死锁。
须要注意的是,可重入锁加锁和解锁的次数要相等。
C==0代表未得到锁,Else表示已经得到锁,这时对state加1,相应的,每次释放锁都会对state减1
2.4.乐观锁/悲观锁
乐观锁/悲观锁不是指具体类型的锁,而是看待并发的角度。
悲观锁认为存在不少并发更新操做,采起加锁操做,若是不加锁必定会有问题
乐观锁认为不存在不少的并发更新操做,不须要加锁。数据库中乐观锁的实现通常采用版本号,Java中可以使用CAS实现乐观锁。
cas无锁机制:即compare and swap 或者 compare and set,涉及到三个操做数,数据所在的内存值,预期值,新值。当须要更新时,判断当前内存值与以前取到的值是否相等,若相等,则用新值更新,若失败则重试,通常状况下是一个自旋操做,即不断的重试。
关于cas具体可参考什么是cas机制?
2.5.分段锁
分段锁是一种锁的设计,并非一种具体的锁。对于ConcuttentHashMap就是经过分段锁实现高效的并发操做。
2.6.自旋锁
自旋锁是指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁。好处是减小上下文切换,缺点是一直占用CPU资源。
2.7.偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,而且是针对Synchronized
。在Java 5经过引入锁升级的机制来实现高效Synchronized
。这三种锁的状态是经过对象监视器在对象头中的字段来代表的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。下降获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另外一个线程所访问,偏向锁就会升级为轻量级锁,其余线程会经过自旋的形式尝试获取锁,不会阻塞,提升性能。
重量级锁是指当锁为轻量级锁的时候,另外一个线程虽然是自旋,但自旋不会一直持续下去,当自旋必定次数的时候,尚未获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其余申请的线程进入阻塞,性能下降。
下面是详解
jdk1.6中对Synchronized锁作的优化,首先了解下对象头(Mark Word):
运行时JVM内存布局
Mark Word在不一样锁状态下的标志位存储
从jdk1.6开始为了减小得到锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。锁共有四种状态,级别从低到高分别是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。随着竞争状况锁状态逐渐升级、锁能够升级但不能降级。
偏向锁的获取和撤销
HotSpot做者通过研究发现,大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入偏向锁。
线程1检查对象头中的Mark Word中是否存储了线程1,若是没有则CAS操做将Mark Word中的线程ID替换为线程1。此时,锁偏向线程1,后面该线程进入同步块时不须要进行CAS操做,只须要简单的测试一下Mark Word中是否存储指向当前线程的偏向锁,若是成功代表该线程已经得到锁。若是失败,则再须要测试一下Mark Word中偏向锁标识是否设置为1(是不是偏向锁),若是没有设置,则使用CAS竞争锁,若是设置了,则尝试使用CAS将偏向锁指向当前线程
偏向锁的竞争结果:
根据持有偏向锁的线程是否存活
1.若是不活动,偏向锁撤销到无锁状态,再偏向到其余线程
2.若是线程仍然活着,则升级到轻量级锁
偏向锁在Java6和Java7中默认是开启的,可是在应用程序启动几秒后才激活,若是有必要能够关闭延迟:
-XX:BiasedLockingStartupDelay=0
若是肯定应用程序中全部的锁一般状况下处于竞争状态,能够经过JVM参数关闭偏向锁:
-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁。
-XX:BiasedLockingStartupDelay=0 -XX:+TraceBiasedLocking
轻量级锁膨胀:
1.线程在执行同步块以前,JVM会在当前栈桢中建立用于存储锁记录的空间(Lock record),并将对象头中的Mark Word复制到锁记录中(Displaced Mark Word)。
2.而后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针
3.若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程尝试使用自旋来获取锁
偏向锁、轻量级锁、重量级锁的优缺点
1.偏向锁是为了不某个线程反复得到/释放同一把锁时的性能消耗,若是仍然是同个线程去得到这个锁,尝试偏向锁时会直接进入同步块,不须要再次得到锁。
2.而轻量级锁和自旋锁都是为了不直接调用操做系统层面的互斥操做,由于挂起线程是一个很耗资源的操做。
为了尽可能避免使用重量级锁(操做系统层面的互斥),首先会尝试轻量级锁,轻量级锁会尝试使用CAS操做来得到锁,若是轻量级锁得到失败,说明存在竞争。可是也许很快就能得到锁,就会尝试自旋锁,将线程作几个空循环,每次循环时都不断尝试得到锁。若是自旋锁也失败,那么只能升级成重量级锁。
3.可见偏向锁,轻量级锁,自旋锁都是乐观锁。
逃逸分析:
逃逸分析:通俗一点讲,当一个对象的指针被多个方法或线程引用时,咱们称这个指针发生了逃逸,必须在JIT里完成
若是能证实一个对象不会逃逸到方法或线程以外,也就是别的方法或线程没法经过任何途径访问到这个对象,则能够为这个变量进行一些高效的优化:
从jdk1.6开始默认开启:
开启: -XX:+DoEscapeAnalysis
关闭: -XX:-DoEscapeAnalysis
锁粗化:
若是虚拟机探测到有这样一串零碎的操做都对同一个对象加锁,将会把加锁同步的范围扩展到整个操做序列的外部,这样就只须要加锁一次就够了
3.1.Synchronized与ReentrantLock的区别
ReentrantLock = 一个AQS同步器(维护同步状态) + 一个AQS同步队列 + 多个Condition等待队列
3.2 ReentrantLock继承体系类图
ReentrantLock#lock()方法时序图
以上内容大部分转自http://www.javashuo.com/article/p-stjweyux-nh.html
4 分布式锁
目前几乎不少大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉咱们“任何一个分布式系统都没法同时知足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时知足两项。”因此,不少系统在设计之初就要对这三者作出取舍。在互联网领域的绝大多数的场景中,都须要牺牲强一致性来换取系统的高可用性,系统每每只须要保证“最终一致性”,只要这个最终时间是在用户能够接受的范围内便可。
在不少场景中,咱们为了保证数据的最终一致性,须要不少的技术方案来支持,好比分布式事务、分布式锁等。有的时候,咱们须要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了不少并发处理相关的API,可是这些API在分布式场景中就无能为力了。也就是说单纯的Java Api并不能提供分布式锁的能力。因此针对分布式锁的实现目前有多种方案
分布式锁应该具有的条件
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行; 2、高可用的获取锁与释放锁; 3、高性能的获取锁与释放锁; 4、具有可重入特性; 5、具有锁失效机制,防止死锁; 六、具有非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
针对分布式锁的实现,目前比较经常使用的有如下几种方案:
基于数据库实现分布式锁
基于缓存(Redis,memcached,tair)实现分布式锁
基于Zookeeper实现分布式锁
实现方式参考分布式锁的简单入门以及三种实现方式介绍
java分布式看这篇就够了(redis实现分布式锁的不一样方式)
关于redis开源分布式锁redisson原理介绍参考