多线程进阶:锁的策略


提示:以下是本篇文章正文内容,Java系列学习将会持续更新

1. 独占锁和共享锁

共享锁:Share Lock (S锁), ‘读’操作之间不互斥,‘写’操作互斥,‘读+写’操作互斥。
独占锁:exclusive Lock (X锁), 所有操作之间都互斥。

如果某个场景下“一写多读”,使用读写锁效率就很高。
因为只进行读数据,多个线程同时读取一个数据,不会造成线程不安全的情况。只有修改同一个数据才会线程不安全

Java中的读写锁

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();

// readLock之间不互斥,readLock和writeLock之间互斥,writeLock之间互斥

readLock.lock(); // 主线程加到读锁

Thread t = new Thread() {
	@Override
	public void run() {
		readLock.lock(); // 主线程已经抢到读锁,子线程依然可以抢到锁
		//writeLock.lock(); // 写锁就不行了
		System.out.println("子线程也可以加读锁成功");
	}
};
        
t.start();

回到目录…

2. 可重入锁和不可重入锁

可重入锁:一个线程已经持有lock1锁的情况下,允许再次获取到lock1同一把锁。
synchronizedReentrantLock都是可重入锁。

public static void main(String[] args) {
	Object lock = new Object();
	synchronized (lock) {   // main 线程已经对 lock 加锁了
		synchronized (lock) {   // main 线程再次对 lock 请求锁(处于已经锁上状态)
			System.out.println("这里打印了就说明了 sync 锁是可重入锁");
		}
	}
}

回到目录…

3. 公平锁和非公平锁

公平锁:有多个线程按照申请锁的顺序来获取锁,就是说,如果一个线程组里面,能够保证每个线程都能拿到锁。
例如:ReentrantLock可以通过传入 fair = true / false 来控制是否公平, 默认是不公平。(fair = true,使用的同步队列FIFO)
非公平锁:获取锁的方式是随机的,保证不了每个线程都能拿到锁,会存在有的线程饿死,一直拿不到锁,例如:synchronized

总结:非公平锁性能高于公平锁,更能重复利用CPU的时间

回到目录…

4. 乐观锁和悲观锁

不是真正意义上的锁,而是在多线程的情况下,评估并发的情况来设计锁
乐观锁: 多个线程获取同一个共享资源时,乐观地认为它们不会修改数据,所以不去上锁。或者只有极个别线程去修改数据,可以采用轻量级锁方式的进行并发控制。cas是乐观锁,通过原子性来保证数据的同步
悲观锁: 不管多个线程是否会频繁地修改同一个共享资源,必须使用互斥的方式(锁lock)来进行并发控制。如synchronizedReentrantLock

总结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁高

什么是 CAS?

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
 1. 比较 A 与 V 是否相等。(比较)
 2. 如果比较相等,将 B 写入 V。(交换)
 3. 返回操作是否成功
原理反复循环(操作 + 判断:实际值==预期值 ?结束循环:继续操作+判断)

CAS的应用
 1. 实现java.util.concurrent.atomic 包下的原子类
  AtomicInteger atomicInteger = new AtomicInteger(0);
  // 相当于 i++
  atomicInteger.getAndIncrement();
 2. 实现了自旋锁

回到目录…

5. 互斥锁和自旋锁

从实现原理上来讲

Mutex(互斥锁)属于sleep-waiting类型的锁。例如在一个单核的机器上有两个线程(线程A和线程B)。假设线程A想要去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞,此时会将线程A置于等待队列中,等待线程B释放锁来唤醒线程A。
而Spin lock(自旋锁)适用于多核计算机下,它属于busy-waiting类型的锁,如果线程A请求不到锁,不会引起线程调度(让出CPU),而是一直在那里循环看是否该自旋锁的持有者已经释放了锁,并不停的进行锁请求,直到得到这个锁为止。

自旋锁的使用

 ①我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP(多核)的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。
 ②“自旋锁”的作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠(线程调度),所以自旋锁的效率远高于互斥锁。
 ③自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。所以自旋锁比较适用于锁持有者持有锁时间比较短的情况
 ④试图递归地获得自旋锁必然会引起死锁: 递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。

回到目录…


总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是Java多线程的学习,认识了不同的锁策略,以及这些策略的应用场景。之后的学习内容将持续更新!!!

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只咸鱼。。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值