读写锁
锁机制引入了读写锁特性:ReadWriteLock接口和唯一的实现类ReentrantReadWriteLock。读写锁是锁机制的最大改进之一,提供了将读和写分开处理的能力。ReentrantReadWriteLock有两个锁,一个是读操作锁,另一个是写操作锁。读操作锁允许多个线程同时访问,但是写操作锁只允许一个线程进行访问,在一个线程执行写操作时,其他线程不能执行读操作。在进行写操作加锁时,必须等待所有的读操作锁都释放之后才能实施写操作加锁。
我们依然以计数器的例子来说明ReadWriteLock的用法,假设只有一个线程更新计数器的值,通过ReadWriteLock的writeLock方法取得写操作锁,进行写操作的同步;其他线程都是读取计数器的值用来显示,通过ReadWriteLock的readLock方法取得读操作锁,进行读操作的同步。示例代码如下:
public class ReadWriteCountDemo {
public static void main(String[] args){
ReadWriteCounter counter = new ReadWriteCounter();
CountReader reader = new CountReader(counter);
CountWriter writer = new CountWriter(counter);
System.out.println("main:创建读写线程");
Thread r1 = new Thread(reader);
Thread r2 = new Thread(reader);
Thread r3 = new Thread(reader);
Thread w = new Thread(writer);
System.out.println("main:启动读写线程");
w.start();
r1.start();
r2.start();
r3.start();
System.out.println("main:等待读写线程");
try {
w.join();
r1.join();
r2.join();
r3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:退出");
}
}
class CountReader implements Runnable{
ReadWriteCounter counter = null;
CountReader(ReadWriteCounter counter){
this.counter = counter;
}
@Override
public void run() {
for(int i=0 ;i<5; i++){
long value = counter.get();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class CountWriter implements Runnable{
ReadWriteCounter counter = null;
CountWriter(ReadWriteCounter counter){
this.counter = counter;
}
@Override
public void run() {
for(int i=0; i<5; i++){
counter.increase();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ReadWriteCounter{
private long count = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void increase(){
System.out.println("写操作:申请写操作锁");
lock.writeLock().lock();
System.out.println("写操作:获得写操作锁");
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("写操作:完成更新计数器。计数器值:" + count + ",释放写操作锁");
lock.writeLock().unlock();
}
}
public long get(){
System.out.println(Thread.currentThread().getName() + ":读操作:申请读操作锁");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + ":读操作:获得读操作锁");
long tmp = count;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + ":读操作:计数器值" + tmp + ",释放读操作锁");
lock.readLock().unlock();
}
return tmp;
}
}
程序执行日志:
main:创建读写线程
main:启动读写线程
写操作:申请写操作锁
写操作:获得写操作锁
main:等待读写线程
Thread-1:读操作:申请读操作锁
Thread-2:读操作:申请读操作锁
Thread-0:读操作:申请读操作锁
写操作:完成更新计数器。计数器值:1,释放写操作锁
Thread-1:读操作:获得读操作锁
Thread-2:读操作:获得读操作锁
Thread-0:读操作:获得读操作锁
写操作:申请写操作锁
Thread-0:读操作:计数器值1,释放读操作锁
Thread-2:读操作:计数器值1,释放读操作锁
Thread-1:读操作:计数器值1,释放读操作锁
写操作:获得写操作锁
Thread-0:读操作:申请读操作锁
Thread-2:读操作:申请读操作锁
Thread-1:读操作:申请读操作锁
写操作:完成更新计数器。计数器值:2,释放写操作锁
Thread-0:读操作:获得读操作锁
Thread-2:读操作:获得读操作锁
Thread-1:读操作:获得读操作锁
写操作:申请写操作锁
Thread-2:读操作:计数器值2,释放读操作锁
Thread-1:读操作:计数器值2,释放读操作锁
Thread-0:读操作:计数器值2,释放读操作锁
写操作:获得写操作锁
Thread-2:读操作:申请读操作锁
Thread-0:读操作:申请读操作锁
Thread-1:读操作:申请读操作锁
写操作:完成更新计数器。计数器值:3,释放写操作锁
Thread-2:读操作:获得读操作锁
Thread-0:读操作:获得读操作锁
Thread-1:读操作:获得读操作锁
Thread-0:读操作:计数器值3,释放读操作锁
Thread-2:读操作:计数器值3,释放读操作锁
写操作:申请写操作锁
Thread-1:读操作:计数器值3,释放读操作锁
写操作:获得写操作锁
Thread-2:读操作:申请读操作锁
Thread-1:读操作:申请读操作锁
Thread-0:读操作:申请读操作锁
写操作:完成更新计数器。计数器值:4,释放写操作锁
Thread-2:读操作:获得读操作锁
Thread-1:读操作:获得读操作锁
Thread-0:读操作:获得读操作锁
Thread-2:读操作:计数器值4,释放读操作锁
写操作:申请写操作锁
Thread-0:读操作:计数器值4,释放读操作锁
Thread-1:读操作:计数器值4,释放读操作锁
写操作:获得写操作锁
Thread-1:读操作:申请读操作锁
Thread-2:读操作:申请读操作锁
Thread-0:读操作:申请读操作锁
写操作:完成更新计数器。计数器值:5,释放写操作锁
Thread-1:读操作:获得读操作锁
Thread-0:读操作:获得读操作锁
Thread-2:读操作:获得读操作锁
Thread-0:读操作:计数器值5,释放读操作锁
Thread-2:读操作:计数器值5,释放读操作锁
Thread-1:读操作:计数器值5,释放读操作锁
main:退出
可以看到:
- 进行写操作加锁时,必须等待所有的读操作锁释放。
- 写操作加锁后,其他读操作必须等待写操作锁释放之后,才能再进行读操作加锁。