暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

常见锁类型

架构师成长 2020-05-03
322

线程锁、进程锁、分布式锁

线程锁:实现多线程安全

进程锁:保证多进程安全

分布式锁:多机多实例实现同步


公平锁、非公平锁

公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁。

非公平锁:非公平锁不会按照申请锁的顺序来获取资源,处于争用状态的线程,哪一个率先获得锁是不确定的。


从资源使用的角度上来讲,公平锁内部还需要额外维护一个线程队列,并且保证这个队列内的线程的有序性,这无疑是更复杂并且需要额外的内存和开销的。相比较而言非公平策略的锁更轻便性能更好。一般对资源争用顺序不敏感的场景优先使用非公平锁。


非公平锁吞吐量比公平锁大,但是在高并发的情况下,有可能造成优先级反转或饥饿现象。


Java ReentrantLock 可以指定构造方法的boolean类型来指定是公平锁还是非公平锁,默认是非公平锁。synchronized则是一种非公平锁。


自旋锁

请求锁的线程,线程在请求锁失败后会进入 等待阻塞 状态并让出当前的 CPU时间片。然而处理器进行线程切换也是一笔开销。为了减少这种场景下的开销时间。可以使用 自旋锁


其核心概念在于不让等待资源的线程进入等待阻塞状态,在短时间内占用CPU资源并等待锁的释放。 这个设计的缘由,是设计者发现很多后台线程占用锁的时间并不是很长,也就是说每一个线程占用锁很短的时间后就会释放这个锁,比起频繁的CPU线程切换,还不如让等待线程进行短暂的等待。


自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK6中已经变为默认开启,并且引入了自适应的自旋锁。自适应意味着自旋的时间不在固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当使用传统的方式去挂起线程了。


可重入锁、不可重入锁

在 Java 中可重入的概念是指某个线程在获取到一个对象的锁之后,可以再次获取到这个对象的锁,其重复获取的次数大于1。在递归调用中可以利用到这个特点。

一般来说某个锁被线程持有后,其他的线程就不能在锁被释放之前去访问。但是有些线程需要多次调用加锁的方法/代码块。会有一个计数器为重复获取锁的次数进行计数,每重复获取一次就加一,每释放一次就减一。直到计数器值为 0 即表示这个锁已经被这个线程完全释放掉。


典型的应用:Java 中synchronized 关键字、对象锁、ReentrantLock 都是 可重入锁


乐观锁、悲观锁

乐观锁:假设当前资源没有被多个线程争用,假设不会发生冲突,只在提交的时候才校验数据的一致性。常用版本号、时间戳等设计来实现乐观锁。

悲观锁:与之对应,假设会发生冲突,并且主动屏蔽可能违反数据一致性的操作。

悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。


Java 中的 synchronized 关键字、Lock 对象都属于悲观锁;auto 原子类则属于乐观锁,其实现使用了 CAS。


MySQL表锁、行锁属于悲观锁。InnoDB 引擎下引入了基于 mvcc 版本控制的乐观锁,适用于查询操作。mvcc 即通过给数据行附加一个版本号来确保数据对更新的敏感。InnoDB 会在每行数据后添加两个额外的隐藏的值来实现MVCC。在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。


共享锁、排它锁、读锁、写锁

读锁:共享锁(S锁)又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。Java 中ReentrantReadWriteLock.ReadLockCountDownLatch、Semaphore属于共享锁。

写锁:排他锁(X锁)又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。Java 中synchronized、ReentrantReadWriteLock.WriteLock、ReentrantLock属于排他锁。


参考:

https://www.jianshu.com/p/632de967958b


文章转载自架构师成长,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论