平常总是看到各式各样的锁类型,但是却没有一个固定或者清晰的分类。这里记录一下平时有见到的讨论的锁类型以及一些典型的实现。
一 公平锁/非公平锁
当某些资源被加锁之后,争用这个锁的线程很多。这时争用锁的线程,由谁来获取到资源的问题。公平锁 的作用在于面对存在争用的资源时,让争用线程排队获取,类似一个FIFO队列一样按顺序来获取和使用资源。非公平锁 则不会按照这种顺序来获取资源,处于争用状态的线程,哪一个率先获得锁是不确定的。
在Java中,ReentrantLock 是实现了这种模式的。默认的构造函数提供了一个 非公平锁 的实现。如果你想要得到一个公平锁,那么可以使用另一个含参的构造函数
Reentrantlock(true)
来得到一个公平锁。其实这里可以深究一下,为什么默认的锁实现会是一个非公平策略。从资源使用的角度上来讲,公平锁内部还需要额外维护一个线程队列,并且保证这个队列内的线程的有序性,这无疑是更复杂并且需要额外的内存和开销的。相比较而言非公平策略的锁更轻便性能更好。
所以在平常使用的时候,一般对资源争用顺序不敏感的场景最好都优先使用非公平锁。
二 自旋锁
请求锁的线程,线程在请求锁失败后会进入 等待阻塞 状态并让出当前的 CPU时间片。然而处理器进行线程切换也是一笔开销。为了减少这种场景下的开销时间。可以使用 自旋锁。
其核心概念在于不让等待资源的线程进入等待阻塞状态,在短时间内占用CPU资源并等待锁的释放。 这个设计的缘由,是设计者发现很多后台线程占用锁的时间并不是很长,也就是说每一个线程占用锁很短的时间后就会释放这个锁,比起频繁的CPU线程切换,还不如让等待线程进行短暂的等待。
自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK6中已经变为默认开启,并且引入了自适应的自旋锁。自适应意味着自旋的时间不在固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当使用传统的方式去挂起线程了。
二 可重入锁/不可重入锁
在 Java 中可重入的概念是指某个线程在获取到一个对象的锁之后,可以再次获取到这个对象的锁,其重复获取的次数大于1。在递归调用中可以利用到这个特点。
一般来说某个锁被线程持有后,其他的线程就不能在锁被释放之前去访问。但是有些线程需要多次调用加锁的方法/代码块。会有一个计数器为重复获取锁的次数进行计数,每重复获取一次就加一,没释放一次就减一。直到计数器值为 0 即表示这个锁已经被这个线程完全释放掉。
典型的应用:synchronized 关键字与 ReentrantLock 都是 可重入锁。
三 乐观锁/悲观锁
这一组概念主要在于 对资源占用的预设态度 上。
乐观锁,即假设当前资源没有被多个线程争用,假设不会发生冲突,只在提交的时候才校验数据的一致性。
悲观锁,与之对应,假设会发生冲突,并且主动屏蔽可能违反数据一致性的操作。
这两者本质上来说就是设计思想。不同的语言或者工具上的具体实现也各不一样。Java 中的 synchronized 关键字、lock 对象都属于悲观锁;auto 原子类则属于乐观锁,其实现使用了 CAS。MySQL 数据库中对于结构性修改的操作也可以划拨为使用悲观锁观念的操作内容。而 InnoDB 引擎下引入了基于 mvcc 版本控制的乐观锁,适用于查询操作。mvcc 即通过给数据行附加一个版本号来确保数据对更新的敏感。InnoDB 会在每行数据后添加两个额外的隐藏的值来实现MVCC。在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。
四 共享锁与排它锁
这是一个我在 MySQL 数据库中看到的概念。两个锁之间的主要区别在于兼容性。数据被加了共享锁,那么这部分数据还可以被其他的线程或操作访问到,比如另一个操作也想要查询这一部分数据并向其加一个共享锁。那么这个操作是可行的。但是一个更新数据的操作想要向这部分数据加排它锁并执行更新操作就会失败,因为这个时候数据集上已经挂上了一个共享锁。
具体的表现是在 MySQL-MyISAM 引擎下的表级锁。
两者之间的兼容性
兼容性五 读写锁
// TODO
网友评论