一篇关于java中锁的文章,看完之后茅塞顿开

https://blog.csdn.net/qq_41931837/article/details/82314478

常见的锁

Synchronized和Lock

其实我们真正用到的锁也就那么两三种,只不过依据设计方案和性质对其进行了大量的划分。
以下一个锁是原生语义上的实现
Synchronized,它就是一个:非公平,悲观,独享,互斥,可重入的重量级锁
以下两个锁都在JUC包下,是API层面上的实现
ReentrantLock,它是一个:默认非公平但可实现公平的,悲观,独享,互斥,可重入,重量级锁。
ReentrantReadWriteLocK,它是一个,默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重量级锁。
 
 

synchronized的优势

synchronized是在JVM层面上实现的, 不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行 ,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

应用场景

在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
 
 

按照其性质分类

公平锁/非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁 。有可能,会造成优先级反转或者饥饿现象。对于Java ReentrantLock而言, 通过构造函数指定该锁是否是公平锁,默认是非公平锁。 非公平锁的优点在于吞吐量比公平锁大。对于 Synchronized而言,也是一种非公平锁 。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式 。悲观的认为,不加锁的并发操作一定会出问题。 乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。 从上面的描述我们可以看出, 悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景 ,不加锁会带来大量的性能提升。 悲观锁在Java中的使用,就是利用各种锁乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

独享锁/共享锁

独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有 对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁 。读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 对于Synchronized而言,当然是独享锁。

互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。互斥锁在Java中的具体实现就是 ReentrantLock 读写锁在Java中的具体实现就是ReentrantReadWriteLock

可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁, 其名字是Reentrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。 可重入锁的一个好处是可一定程度避免死锁。
public sychrnozied void test () {
xxxxxx;
test2();
}
public sychronized void test2() {
yyyyy;
}
在上面代码段中,执行 test 方法需要获得当前对象作为监视器的对象锁,但方法中又调用了 test2 的同步方法。
如果锁是具有可重入性的话,那么该线程在调用 test2 时并不需要再次获得当前对象的锁,可以直接进入 test2 方法进行操作。
如果锁是不具有可重入性的话,那么该线程在调用 test2 前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次获得。
如果锁是不具有可重入性特点的话, 那么线程在调用同步方法、含有锁的方法时就会产生死锁。

按照设计方案来分类

锁优化里涉及到的,都在上一篇博客里提到了,大概有以下这么几种

自旋锁/自适应自旋锁

在Java中, 自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁 ,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。更多具体的细节见上一篇博客里对自旋锁的描述
http://blog.csdn.net/sinat_33087001/article/details/77644441#t27

锁粗化/锁消除

锁消除 是指虚拟机即时编译器在运行时, 对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除 。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。
如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。 如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部
锁粗化和消除其实设计原理都差不多,都是为了减少没必要的加锁 更多详情见以下链接的上一篇博文介绍
http://blog.csdn.net/sinat_33087001/article/details/77644441#t27

偏向锁/轻量级锁/重量级锁

这三种锁是指锁的状态 ,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。 这三种锁的状态是通过对象监视器在对象头中的字段来表明的
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁 ,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。 重量级锁会让其他申请的线程进入阻塞,性能降低。
http://blog.csdn.net/sinat_33087001/article/details/77644441#t27

分段锁

分段锁其实是一种锁的设计,并不是具体的一种锁, 对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作 。我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想, ConcurrentHashMap中的分段锁称为Segment ,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即 内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)
当需要put元素的时候,并不是对整个hashmap进行加锁,而是 先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁 ,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。 分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
 

你可能感兴趣的:(一篇关于java中锁的文章,看完之后茅塞顿开)