读写锁(ReentrantReadWriteLock):分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
读写锁机制
读-读不互斥
读-写互斥
写-写互斥
如果只读数据,可以多个线程读,但是不能同时有写的线程,上读锁
如果需要写数据,则不能有读的线程,且只能有一个线程写数据,上写锁。(如果上了读锁,需要先释放读锁)
线程进入读锁的前提条件:
没有其他线程的写锁,
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
1、示例
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLock {
public static void main(String[] args) {
final Queue3 queue=new Queue3();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while(true){
queue.read();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
int data=new Random().nextInt(1000);
queue.write(data);
}
}
}).start();
}
}
}
class Queue3{
private int data;
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void read() {
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "have read data :" + data);
} catch (Exception e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
}
public void write(int data) {
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be write to write data!");
Thread.sleep((long)(Math.random()*1000));
this.data=data;
System.out.println(Thread.currentThread().getName() + "have write data :" + data);
} catch (Exception e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}
实例公有三个线程读,三个线程写数据,当读取的时候,可以有多个线程去读取数据,但是如果有线程去写数据的时候,只有等到数据写完成之后,才能读数据。
2、应用
缓存系统
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key) {
rwl.readLock().lock();
Object value = null;
try {
value = cache.get(key);
if (value == null) {
rwl.readLock().unlock();//必须先释放读锁,才能获取写锁
rwl.writeLock().lock();
try {
if (value==null) {//获取写锁后再次判断对象是否为null,是为了当前等待的写线程不在执行赋值,直接取缓存数据
value = "aaa";// 实际queryDB()
cache.put(key,value);
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
return value;
}
}
HashMap作为缓存是非线程安全的,事例使用读写锁的读锁和写锁来保证拿到的缓存cache是线程安全的。在读操作 get()时,需先上读锁,这样并发访问读时不会被阻塞。写操作在更新缓存cache必须提前获取写锁, 当写锁被获取后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放 之后,其他读写操作才能继续。