lock-上

lingxing_beijing-006

引言

这些分类并不全指锁的状态,有的指锁的特性,有的指锁的设计

公平锁

​ 多个线程按照申请锁的顺序获取锁

非公平锁

​ 多个线程不按照申请锁的顺序获取锁

​ 有可能后申请的线程比先申请的优先获得锁。有可能造成优先级反转h或饥饿现象

​ 对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

​ 对于Synchronized而言,也是一种非公平锁。其并不像ReentranLock是通过AQS来实现线程调度,因而并没有任何办法使其变成公平锁。

可重入锁

​ 广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,内层仍然可以使用,并且不发生死锁(前提是同一个对象或者class),这样的锁叫做可重入锁。

​ ReentrantLock或synchronized都是可重入锁

不可重入锁

​ 不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。

​ 独享锁和共享锁在C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就会发现,他俩一个是独享锁一个是共享锁。

独享锁

​ 该锁每一次只能被一个线程持有

共享锁

​ 可被多个线程共同持有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以共享的,但写锁每次只能被独占

读锁的共享可保证并发读是非常高效的,但附带写的操作都是互斥的,例如读写,谢谢,写读。共享锁和独占锁通过AQS来实现,通过实现不同的方法,来实现独享或共享。对于Synchronized而言,当然是独享锁

tianshi_diaoxiang-011

互斥锁

​ 在访问共享资源之前对其进行加锁,在访问完成之后进行解锁操作。加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前线程解锁。

​ 解锁时有一个以上的线程在阻塞,那么该锁上的所有线程都被编程为就绪状态,第一个变为就绪状态的线程又执行加锁操作,那么其他线程又会进入等待。此方式下,只有一个线程能够访问被互斥锁保护的资源

读写锁

​ 读写锁既是互斥锁,也是共享锁。read模式是共享锁,write模式是互斥锁(排他锁)

​ 读写锁的三种状态:读写锁,写加锁和不加锁

​ 在Java中的具体实现:ReadWriteLock

​ 只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态的锁,这也是它实现高并发的原因。当其处在写状态下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其他线程获取其读状态锁,但不允许获取它的写状态锁直到所有线程的读都释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读状态锁感知到有线程想要获得写状态锁时,会阻塞其后想要获取读状态锁的线程,所以读写锁非常适合在 读 远多于 写操作的情况下使用

悲观锁

​ 总是假设最坏的情况,每次去拿数据都会认为别人会修改,所以每次拿数据的时候都会上锁。这样别人拿这个数据就会阻塞直到它拿到共享锁(共享锁每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程)。传统的关系型数据库里边就用到了很多类似的锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。Java中Synchronized和ReentrantLock等独占锁j就是悲观锁思想的实现。

乐观锁

​ 总假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但更新的时候会判断一下在此期间别人有没有更新这个数据,可以使用版本号机制和CAS算法实现。

​ 乐观锁适用于多读的应用类型,可以提高吞吐量,就像数据库提供的类似write_condition机制,其实都是提供乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的

fengzhong_yaoye_fengling-007

分段锁

​ 分段锁其实是一种锁的设计,并非具体实现。对于concurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效并发操作

​ 并发容器类的加锁机制是基于更小粒度的分段锁,分段锁也是提升多并发程序性能的重要手段之一。

​ 在并发程序中,串行操作会降低可伸缩性,并且上下文的切换也会降低性能。在锁上发生竞争时,将同时导致这两种问题,使用独占锁时保护受限资源,基本采用串行方式,每次只能有一个线程访问它。所以对于可伸缩性来说最大的威胁就是独占锁

一般有三种降低锁的竞争程度的方式:

1,减少锁的持有时间;2,降低锁的请求频率;3,使用带协调机制的独占锁,这些机制允许更高的并发性。

在某些情况下,我们可以将锁分解技术进一步扩展为一组独立对象上的锁进行分解,这称为分段锁。

简单讲解:

​ 容器内有多把锁,每把锁用于锁容器中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是concurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

举例:

​ 在concurrentHashMap中使用一个包含16个锁的数组,每个锁保护散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合适的散列算法使关键字均匀分布,那么这大约能使对锁的请求减少1/16,正是这项技术使concurrentHashMap支持的并发写入达到16个

xiaochouyu-011

锁的状态:

1,无锁状态

2,偏向锁状态

3,轻量级锁状态

4,重量级锁状态

锁的状态会通过对象监视器在对象头部中的字段表明。

四种状态会随着竞争的升级而升级,且不可逆,不可降级

四种锁并非Java语言中的锁,而是jvm为了提高锁的获取与释放效率而做的优化(用Synchronized时

偏向锁

​ 指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价

轻量级锁

​ 当锁是偏向锁时,被另一个线程访问,偏向锁会升级为轻量级锁,其他线程会通过自旋形式尝试获取,不会阻塞,提高性能

重量级锁

​ 指当前锁为轻量级锁时,另一个线程虽然自旋,但不会一直持续下去,当达到一定次数还没得到,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低

参考文章

你知道Java里有多少种锁吗?(15种锁最全总结)

posted @ 2022-04-15 15:50  lifelikeplay  阅读(22)  评论(0编辑  收藏  举报