为什么读写要互斥,读的时候不能写,写的时候不能读?
举个例子,用户A正在读的时候,用户B对数据进行了写操作,就会出现A读到的数据不准确。A在写的时候,B去读,则读到的数据也是不准确的。所以读和写要互斥。
读写锁的特点
1、读锁与写锁互斥。
2、读锁之间不互斥。
3、写锁与写锁互斥。
读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这些特性由jvm底层控制,代码中加上相应的锁即可。使用的时候注意,读的时候上读锁,写的时候上写锁就OK!
读写锁对应的api
ReadWriteLock(接口)/ReentrantReadWriteLock(实现类)
ReentrantReadWriteLock有两个静态的内部类
ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock
他们的类声明如下
public static class WriteLock implements Lock, java.io.Serializable
public static class ReadLock implements Lock, java.io.Serializable
package cn.iktz.thread.demo;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest2 {
public static void main(String args[]) {
final ThreadData threadData = new ThreadData();
//for循环会开启6个线程,3个线程get数据,3个线程put数据,
//当然,get到的数据,都是最后一个put进入的数据。
for (int i = 0; i < 3; i++) {
new Thread() {
public void run() {
while (true) {
try {
threadData.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run(){
while (true) {
try {
threadData.put(new Random().nextInt(10000));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
}
}
static class ThreadData {
private Object data = null;
ReadWriteLock rwl = new ReentrantReadWriteLock();
public void get() throws Exception {
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " --- get method start >>");
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " --- get method end >> data :" + data);
} finally {
rwl.readLock().unlock();
}
}
public void put(Object data) throws Exception {
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " ||| put method start >>");
Thread.sleep((long) (Math.random() * 1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + " ||| put method end >> data :" + data);
} finally {
rwl.writeLock().unlock();
}
}
}
}
java api中关于读写锁实现CacheData的例子
package cn.iktz.thread.demo;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class LoginUser{
public int id;
public String loginName;
public String password;
public static LoginUser getLoginUser(int id){
LoginUser loginUser = new LoginUser();
//dao.selectLoginUserByID(id)
return loginUser ;
}
}
class CachedData {
LoginUser loginUser;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {//第一次读数据的时候,LoginUser是空的
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid) {//如果没有缓存,
loginUser = LoginUser.getLoginUser(01);
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
//应用数据
System.out.println(loginUser);
rwl.readLock().unlock();
}
}
缓存的逻辑
用hashMap实现的一个简易的缓存系统
package cn.iktz.thread.demo;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key) {
rwl.readLock().lock();
Object obj = null;
try {
obj = cache.get(key);
if (obj == null) {
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (obj == null) {
// 查询数据库,
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
} finally {
rwl.readLock().unlock();
}
return obj;
}
}
为何要进行第二次的验证null?
因为,若不判断空,当有n个线程(n>2)走到 rwl.readLock().unlock();rwl.writeLock().lock();的时候,当上了写锁以后,其他的线程就阻塞在了这里,在这里等待,当第一个线程获取了数据往下走了以后,以前等待在这里的线程继续往下走,可能会又去查一次数据。