Java中的各种锁&CAS

画外音:java中的各种锁是高并发的关键,CAS是乐观锁的经典模型。

1. Java中有哪些锁?

● 公平锁 / 非公平锁
● 可重入锁 / 不可重入锁
● 独享锁 / 共享锁
● 互斥锁 / 读写锁
● 乐观锁 / 悲观锁
● 分段锁
● 自旋锁

1. 公平锁:按多线性申请锁的顺序来获取锁。

Java ReentrantLock默认是非公平锁。非公平锁的吞吐量比公平锁答。
Synchronized也是非公平锁,但无法向ReentrantLock一样变成公平锁。
ReentrantLock在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。

2. 可重入锁:可重复递归调用的锁,在外层使用锁的情况下,内层仍可使用,且不会发生死锁。

ReentrantLock和synchronized都是可重入锁。

3. 独享锁/共享锁

独享锁:该锁每一次只能被一个线程所持有,如ReentrantReadWriteLock中的写锁、synchronized。
共享锁:该锁可被多个线程共有,如ReentrantReadWriteLock里的读锁。

4. 互斥锁 / 读写锁

互斥锁:同独享锁。
读写锁:同ReentrantReadWriteLock

5. 乐观锁 / 悲观锁

悲观锁:总是认为会发生最坏情况每个线程都会修改公共资源。
传统关系型数据库中使用悲观锁的场景:行锁、表锁、写锁、synchronzied、ReentrantReadWriteLock的写锁。

乐观锁:总是认为会发生最好的情况即每个线程都不会去修改,所以不必加锁。
但乐观锁在会更新的时候判断在此期间其他线程是否有更新这个公共临界资源,可以使用版本号机制和CAS算法来现实乐观锁。
乐观锁适用于读多的场景,可提升吞吐量。
数据库提供的write_condition机制就是乐观锁。java concurrent包下的原子变量类就是乐观锁的CAS方式实现的。

6. 分段锁:

分段锁并不是一种锁,是Java7中ConcurrentHashMap的一种设计思想,通过降低锁粒度(把锁定全部Map改成锁每个segment,ConcurrentHashMap有16个Segment即所谓分桶)来提升高并发性能。

7. 自旋锁

CAS(Conmpare And Swap, 比较并替换)是乐观锁的一种实现方式,而CAS算法中又涉及到自旋锁。
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
自旋锁的例子:

import java.util.concurrent.atomic.AtomicReference;
public class Test {
	private AtomicReference<Thread> cas = new AtomicReference<Thread>();

	public void lock() {
		Thread current = Thread.currentThread();
		// 利用CAS
		while (!cas.compareAndSet(null, current)) {
			// DO nothing
		}
	}

	public void unlock() {
		Thread current = Thread.currentThread();
		cas.compareAndSet(current, null);
	}
}

lock()方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用unlock方法释放了该锁。

使用自旋锁要注意的问题:
1、如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
2、上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

自旋锁的优点
● 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
● 而对于非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

自旋锁总结:
● 自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。
● 自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。
● 自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。
● 自旋锁本身无法保证公平性,同时也无法保证可重入性。
● 基于自旋锁,可以实现具备公平性和可重入性质的锁

猜你喜欢

转载自blog.csdn.net/shijinghan1126/article/details/88075793
今日推荐