一、锁的分组归类
线程需不需要锁住同步资源?
锁住:悲观锁
不锁住:乐观锁
获取同步资源失败,需不需要阻塞线程?
阻塞:
不阻塞:自旋锁、适应性自旋锁
不锁住资源,多个线程中只有一个能修改资源成功:无锁
同一个线程执行同步资源时自动获取资源:偏向锁
多个线程竞争同步资源,没有获取资源的线程自旋等待锁释放:轻量级锁
多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒:重量级锁
多个线程竞争同步资源是否需要排队?
排队:公平锁
先尝试插队,插队失败在排队:非公平锁
能不能同时获取同一把锁?
能:可重入锁
不能:不可重入锁
能不能共享一把锁?
能:共享锁
不能:排他锁
1.乐观锁和悲观锁
乐观锁:就是认为自己在操作同步资源的时候,根本不担心有别的线程来修改数据;
悲观锁:总是担心自己在操作同步资源的时候担心有别的线程来操作修改数据
乐观锁采用的是CAS算法,而Java的atomic类递增操作就是CAS自旋实现的。获取树后直接操作,在准备更新同步资源的时候会先判断内存中的同步资源是否被更新,没有更新,呢么就更新内存中的同步资源的值,被更新了就会重试要么报错;悲观锁采用的AQS算法,有两个线程来操作同一个同步资源,那么谁先获取到这个资源就证明谁先获取到了锁,加锁成功操作做资源,其他线程无法操作,等待操作完毕自己会释放锁,再有其他线程争取锁(可知乐观锁更适合读操作,而悲观锁适合写操作)
前面提到了java.util.concurrent下面的原子类,观察源码发现就是使用的CAS算法
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
- unsafe:获取并操作内存的数据
- valueOffset:存储value在AtomicInteger中的偏移量
- value:存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的
我们看到了incrementAndGet()源码时,看到调用了usafe.getAndAddInt(),点进去看到源码getAndInt()循环获取给定对象o中的的偏移量处的值v,然后判断内存值是否等于v。如果相等则将内存值设置为v+delta,否则返回false,继续循环进行重试,知道设置成功才能退出循环,并将旧值返回。整个”比较+更新 “操作封装在compareAndSwaplnt()中,在这里JIT是借助一个CPU指令完成的,属于原子操作,可以保证多个线程都能够看到同一个变量的修改值。(什么是JIT,指的是”在运行时进行编译“)
而CAS就需要三个操作数:需要读写到的内存值V,进行比较的值A,要写入的新值B;当且仅当V值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作
CAS会造成的三个问题:
- ABA问题,CAS需要在操作值的时候检查内存值是否发生了变化,没有发生变化才会更新内存值。但是如果内存之原来是A,后来变成了B,然后又变成了A,那么CAS进行检查的时候会发现值没有变化,但是实际上发生了变化。那么我们需要解决可以在变量前添加版本号,每次变量更新都把版本号加一
- 循环时间长开销大。CAS如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销
- 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。
Java从1.5开始jdk提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作
2、自旋锁和适应性自旋锁
在对于同步代码块中的代码内容过于简单,那么如果我们需要阻塞或唤醒一个线程需要操作系统切换CPU状态来完成,那么这样的话效率会很低。那么现实场景中同步资源的锁定时间都很短,为了这么短的时间去切换线程不值得,那么我们就会使得下一个线程就要稍等一下,就会让这个线程进行自旋,如果自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销,这就是自旋锁。
自旋锁也有缺点,自旋等待随让避免了线程切换的开销,但他要占用处理器时间,如果锁被占用的时间很短,那么自选等待效果会很好,如果被占用时间过长,那么这样自旋只会浪费处理器资源,随意可以通过设置-XX:PerBlockSpin来更改自旋的次数
自旋的实现原理同样也是CAS,AtomicInteger中调用的unsafe进行自增操作的源码中的do-while循环就是自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功
jdk之后自旋锁是默认开启并且引入了人适应性自旋锁,意味着自旋的时间不再固定,而是由上一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而他将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
3、无锁、偏向锁、轻量级锁、重量级锁
这些是指锁的状态,专门针对synchronized的,那么synchronized能实现线程同步呢?
有两个概念:”Java对象头“、”monitor“
java对象头:
synchronized是悲观锁,在操作共享资源的时候就会加锁,那么这个锁就是存在java对象头里面的。以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word、Klass Pointer。
Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身自定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。他会根据对象的状态服用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
Monitor是线程私有的数据结构,每一个线程都有一个可用的monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该所被这个线程占用。那么实现同步就是synchronized通过monitor来实现线程同步,Monitor是依赖于底层的操作做系统的Mutex lock(互斥锁)来实现线程同步的。
那么之前我们提到的在执行一个同步代码块的时候阻塞或唤醒一个java线程需要操作系统切换CPU状态,如果同步代码块内容过于简单那么就会导致转换状态的时间比同步代码快执行的时间还要长,这就是1.6之前synchronized效率低的原因。这种依赖与操作系统Mutex Lock所实现的锁我们称之为”重量级锁“,所以1.6以后为了减少获得锁和释放锁带来的性能消耗,引入了”偏向锁“和”轻量级锁“。
锁一般分为四种状态从低到高:无所、偏向锁、轻量级锁、重量级锁。锁状态只能升级不能降级
无锁:
没有对资源进行锁定,所有线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,他的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源,如果没有冲突就修改成功并退出,否则就会继续循环尝试。
偏向锁:
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。当一个线程访问同步代码快并获取锁时,会在Mark Word里存储锁偏向的线程ID,在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是监测Mark
Word里是否存储者指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下经量减少不必要的轻量级锁执行路径,因为轻量级随的锁获取既释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可
轻量级锁:
是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能
重量级锁:
除了用有所的线程意外的线程都阻塞
4、公平锁和非公平锁
公平锁就是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的有点是等待锁的线程不会饿死。缺点是整体吞吐效率贤惠非公平锁要低,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的对位等待。缺点是出于等待队列中的线程可能会饿死。
----------------------------------------------------------持续更新中----------------------------------------------------------