《蹲坑也能进大厂》多线程系列 - 自旋锁/共享锁/独占锁详解
Posted 花哥编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《蹲坑也能进大厂》多线程系列 - 自旋锁/共享锁/独占锁详解相关的知识,希望对你有一定的参考价值。
前言
多线程系列我们前面已经更新过很多章节,强烈建议小伙伴按照顺序学习:
《蹲坑也能进大厂》多线程系列文章目录[1]
在上中两篇文章我们介绍了悲观/乐观锁、可重入/不可重入锁、公平/非公平锁等概念,今天继续介绍剩下几种常见的锁。
正文
锁的总览
锁可以从不同的角度进行分类,这些分类并不是互斥的,比如一个人既可以是医生,又可以是父亲,也可以是一样女人。分类总览如图:
自旋锁和非自旋锁
•概念
再介绍一下什么是自旋,它是指当线程获取资源失败时,不会进入阻塞,而是一直在原地循环,不断尝试获取锁,直到锁被释放,这个等待的线程马上就可以去获得锁。
自旋锁:多个线程申请锁时,首先会尝试获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。此类型的锁就是自旋锁。
非自旋锁:当某线程无法获取锁时,该线程会被直接挂起,并且不再消耗CPU,当其他线程释放锁后,被挂起的线程会被唤醒。此类型锁即为非自旋锁。
这里用一张流程图来看会更加形象。
使用场景
•多核处理器中,如果线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,此时应当用非自旋锁。如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用非自旋锁。•单核处理器中,一般建议不要使用自旋锁。因为在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。•如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。
自旋锁优缺点
•自旋锁减少了线程在阻塞和唤醒之间切换的频率,那么就是降低了操作系统在用户态和内核态的切换,降低了cpu的损耗,提高了线程在竞争期间的性能。• 自旋锁的时间无法判断,如果线程持有锁的时间短,小于或者等于自旋的时间,那么就很舒服,自旋过了刚好获得了锁,但是很多时候这个锁持有时间长短就要看代码设计了,所以无法预测。
共享锁和独占锁
•概念
共享锁(读锁) 是一种乐观锁,它允许一个资源可以同时被多个读操作访问,或者被一个 写操作访问,但是两者不能同时进行。java的并发包中提供了ReentrantReadWriteLock
,
独占锁(写锁) 是一种悲观的加锁策略,同一时刻只能被一个线程拥有。对ReentrantLock
和Synchronized
而言都是独占锁。如果某个只读线程
获取锁,则其他读线程
都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。
对ReentrantReadWriteLock,其读锁是共享锁,其写锁是独占锁。读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。这里代码演示ReentrantReadWriteLock
的读写锁使用:
public class ReentrantReadWriteLockDemo {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
public static void main(String[] args) {
//两个读操作,两个写操作
new Thread(()->read(),"Thread-1").start();
new Thread(()->read(),"Thread-2").start();
new Thread(()->write(),"Thread-3").start();
new Thread(()->write(),"Thread-4").start();
}
//读操作
public static void read(){
readLock.lock();
System.out.println(Thread.currentThread().getName()+"正在执行【读锁】");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"正在释放【读锁】");
readLock.unlock();
}
}
//写操作
public static void write(){
writeLock.lock();
System.out.println(Thread.currentThread().getName()+"正在执行【写锁】");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"正在释放【写锁】");
writeLock.unlock();
}
}
}
执行结果:
结果分析:同一时间可以执行多个读操作
,但是写操作
只能排队执行。
读写锁优点
•在没有读写锁之前,虽然可以保证线程安全,但是由于多个读操作不能同时进行,造成资源浪费。•在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是没有阻塞的,极大的提高了程序的执行效率。
使用规则
•多个线程只申请读锁,都可以申请到•如果一个线程已经占用了读锁,其他申请写锁的线程会阻塞,知道读锁被释放•如果一个线程占用了写锁,此时其他线程想申请写锁或读锁,都会被阻塞。•总结就是要么多读(同一时刻有多个读操作),要么一写(同一时刻有一个写操作),两者不会同时出现。
可中断锁和不可中断锁
•定义
可中断锁顾名思义就是可以中断的锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
不可中断锁也就是和可中断锁
性质相反,在等待过程中不可以中断自己或被其他线程中断。Java中Synchronized就是不可中断锁。
总结
以上就是关于锁
的介绍,目前锁
的分类分为三个章节全部讲完了,这些知识理论性较强,需要慢慢消化,同时篇幅有限,要想真正掌握还需要多拓展,多尝试在不同场景使用不同类型的锁。
点关注,不迷路
以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花GieGie ,有问题大家随时留言讨论 ,我们下期见 以上是关于《蹲坑也能进大厂》多线程系列 - 自旋锁/共享锁/独占锁详解的主要内容,如果未能解决你的问题,请参考以下文章