JUC线程进阶篇07:ReadWriteLock读写锁
标签: 多线程
段落引用
写写需要互斥
读读不需要互斥
为什么许多要读写锁
互斥锁lock将语句块标记为临界区,当一个线程获取到锁后,其他线程如果需要使用该临界区则必须要等待前一个线程使用完毕后释放锁后才可以使用。
在读写时,假设采用lock锁:如果一个对象有读方法和写方法,那么拥有该对象的某个线程在进行读取或者写入的时候,其他线程就无法进行读取或写入。
这就引出了读写锁Readwritelock:写写需要互斥,读读不需要互斥
Readwritelock
Readwritelock锁的:ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。 即:当拥有该对象的某个线程在进行读取时,其他线程也可以进行读取,但是不能写入操作。当该线程进行写入操作时,其他线程不可以读取和写入。
所以readwritelock 锁,只有在进行写操作的时候,才会阻断其他线程。
即线程同步,当进行读取操作的时候,其他读操作的线程可以进行并发进行,则写操作等待
所有读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。
读写锁比互斥锁允许对于共享数据更大程度的并发。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。
Java并发包中ReadWriteLock是一个接口,主要有两个方法,如下:
public interface ReadWriteLock {
/** * 返回读锁 */
Lock readLock();
/** * 返回写锁 */
Lock writeLock();
}
ReentrantReadWriteLock分析
源码
ReentrantReadWriteLock源码分析如下:
/** 内部类 读锁 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 内部类 写锁 */
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock() {
this(false);
}
/** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
/** 返回用于写入操作的锁 */
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
/** 返回用于读取操作的锁 */
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
abstract static class Sync extends AbstractQueuedSynchronizer {
/** * 省略其余源代码 */
}
public static class WriteLock implements Lock, java.io.Serializable{
/** * 省略其余源代码 */
}
public static class ReadLock implements Lock, java.io.Serializable {
/** * 省略其余源代码 */
}
ReentrantReadWriteLock与ReentrantLock一样,其锁主体依然是Sync,它的读锁、写锁都是依靠Sync来实现的。所以ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样而已,它的读写锁其实就是两个类:ReadLock、writeLock,这两个类都是lock实现。
特征
获取顺序:
- 非公平模式(默认)
当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。 - 公平模式
当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。
当有写线程持有写锁或者有等待的写线程时,一个尝试获取公平的读锁(非重入)的线程就会阻塞。这个线程直到等待时间最长的写锁获得锁后并释放掉锁后才能获取到读锁。
可重入:允许读锁可写锁可重入。写锁可以获得读锁,读锁不能获得写锁。
锁降级:允许写锁降低为读锁
中断锁的获取:在读锁和写锁的获取过程中支持中断
支持Condition:写锁提供Condition实现
监控:提供确定锁是否被持有等辅助方法
代码演示
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** * Created by japson on 4/27/2018. */
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
readWriteLockDemo.set((int)(Math.random() * 101));
}
},"write:").start();
for (int i = 0 ; i < 50 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
readWriteLockDemo.get();
}
}).start();
}
}
}
class ReadWriteLockDemo {
private int number = 0;
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 读number
public void get() {
// 读锁 上锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + ":" + number);
} finally {
// 读锁 释放锁
readWriteLock.readLock().unlock();
}
}
// 写number
public void set(int number) {
// 写锁 上锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName());
this.number = number;
} finally {
// 读锁 释放锁
readWriteLock.writeLock().unlock();
}
}
}