多线程之读写锁
共 8316字,需浏览 17分钟
·
2021-07-13 00:55
前言
在java
中,锁lock
是多线程编程的一个重要组件,可以说凡是涉及到多线程编程,线程安全这一块就无法避开lock
,进一步说就是所有的线程安全都是基于锁实现的,只是从形式上分为隐式锁和显式锁,synchronized
就属于隐式锁,像我们之前分享的可重入锁就属于显式锁,当然显示锁还有很多,我们今天就来看一个很常用的显式锁——读写锁。
读写锁
读写锁是一对互斥锁,分为读锁和写锁。读锁和写锁互斥,让一个线程在进行读操作时,不允许其他线程的写操作,但是不影响其他线程的读操作;当一个线程在进行写操作时,不允许任何线程进行读操作或者写操作。
简单来说就是,写锁会排斥读和写,但是读锁只排斥写,这样的好处就很明显,在读多写少的应用场景下,比其他互斥锁性能要好很多。下面我们通过一段代码说明:
public class Example {
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
ExecutorService executorService = Executors.newCachedThreadPool();
// 写操作1:写锁
executorService.execute(() -> {
writeLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("写count1: " + count.incrementAndGet());
}
writeLock.unlock();
});
// 读操作1:读锁
executorService.execute(() -> {
readLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("读count1: " + count.get());
}
readLock.unlock();
});
// 读操作2:读锁
executorService.execute(() -> {
readLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("读count2: " + count.get());
}
readLock.unlock();
});
// 写操作2:写锁
executorService.execute(() -> {
writeLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("写count2: " + count.incrementAndGet());
}
writeLock.unlock();
});
// 写操作3:写锁
executorService.execute(() -> {
writeLock.lock();
for (int i = 0; i < 5; i++) {
System.out.println("写count3: " + count.incrementAndGet());
}
writeLock.unlock();
});
executorService.shutdown();
}
}
上面的代码中,我们分别有3
次写操作,2
次读操作,然后运行上面的代码:
运行结果图片上我们已经标注了线程休眠情况,根据运行结果虽然写count1
休眠了1000
,读count1
休眠了500
,但因为写锁的存在,排斥了其他读写操作,读只能等写锁释放,所以是先写后读,也就是写锁排斥读;
读count2
休眠了200
,读count1
休眠了500
,虽然加了读锁,但结果还是读count2
先运行,而且期间是读count1
和读count2
交替运行,说明读并不排斥读,而且读后面的写count2
只休眠了100
,但还是在读count1
和读count2
之后运行,说明读排斥写;
写count2
休眠100
,写count3
未休眠,但是写count3
依然在写count2
之后执行,说明写锁排斥写。
综上,我们在示例中分别证明了我们上面的结论:写锁会排斥读和写,但是读锁只排斥写。
总结
读写锁相比于其他互斥锁,优点很明显,也就是前面我们说的:在读多写少的应用场景下,比其他互斥锁性能要好很多。至于原因,我们也通过示例证明了:写锁会排斥读和写,但是读锁只排斥写。
关于读写锁,我觉得今天分享的内容已经比较详细,不仅展示了它的基本用法,同时还通过示例反证了它的互斥原则,所以如果你看明白了今天的内容,那么互斥锁这一款的知识你就掌握了。好了,今天的内容就到这里吧!
- END -