初次接触ReadWriteLock类时也在网上查了很多资料,很容易了解到ReadWriteLock是读写锁,并且读写锁的机制有以下三个特点:
读锁---读锁 (不互斥)
读锁---写锁 (互斥)
写锁---写锁 (互斥)
什么意思呢?
网上很多资料,直接用这三个特点实现一个缓存的例子进行了讲解,但是对小白来说还有那么一丝丝的迷惑(老鸟忽略),下面就逐一演示:
1. 读锁---读锁
main方法里两个线程都在读数据,在read()方法里已经上了读锁且没有解锁(为了测试)
public class ReadWriteLockTest {
public static void main(String[] args) {
final ReadAndWrite raw = new ReadAndWrite();
raw.map.put("data", 1);
//线程1,读数据
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.read();
}
}).start();
//线程2,读数据
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.read();
}
}).start();
}
}
class ReadAndWrite{
ReadWriteLock rwlock = new ReentrantReadWriteLock();//读写锁
Map map = new HashMap();//共享的数据
public void read(){
rwlock.readLock().lock();//上读锁,且没有解锁
System.out.println(Thread.currentThread().getName()+"读开始...");
System.out.println(Thread.currentThread().getName()+"读数据为:"+map.get("data"));
System.out.println(Thread.currentThread().getName()+"读结束...");
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(可以看到两个线程都能不断读数据,记住这个现象,接着看下面的例子进行对比)
运行结果:
Thread-0读开始...
Thread-0读数据为:1
Thread-0读结束...
Thread-1读开始...
Thread-1读数据为:1
Thread-1读结束...
Thread-1读开始...
Thread-1读数据为:1
Thread-1读结束...
Thread-0读开始...
Thread-0读数据为:1
Thread-0读结束...
2. 读锁---写锁
main方法里线程1在读数据,线程2在写数据,将read()方法里的“解读锁”注释掉和不注释分别运行对比结果
public class ReadWriteLockTest2 {
public static void main(String[] args) {
final ReadAndWrite raw = new ReadAndWrite();
raw.map.put("data", 1);
//线程1,读数据
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.read();
}
}).start();
//线程2,写数据
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.write();
}
}).start();
}
}
class ReadAndWrite{
ReadWriteLock rwlock = new ReentrantReadWriteLock();//读写锁
Map map = new HashMap();//共享的数据
public void read(){
rwlock.readLock().lock();//上读锁
System.out.println(Thread.currentThread().getName()+"读开始...");
System.out.println(Thread.currentThread().getName()+"读数据为:"+map.get("data"));
System.out.println(Thread.currentThread().getName()+"读结束...");
<span style="color:#ff0000;">//rwlock.readLock().unlock();//解读锁</span>
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void write(){
rwlock.writeLock().lock();//上写锁
System.out.println(Thread.currentThread().getName()+"写开始...");
double data = Math.random();
map.put("data", data);
System.out.println(Thread.currentThread().getName()+"写数据为:"+data);
System.out.println(Thread.currentThread().getName()+"写结束...");
rwlock.writeLock().unlock();//解写锁
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
read()方法里“解读锁”注释掉,(可以看到只有线程1在读数据,线程2是阻塞状态无法写数据,证明了读锁和写锁的互斥)
运行结果:
Thread-0读开始...
Thread-0读数据为:1
Thread-0读结束...
Thread-0读开始...
Thread-0读数据为:1
Thread-0读结束...
read()方法里“解读锁”不注释,(可以看到线程1和线程2交替的读和写数据,也就是读锁解了,其他线程就可以写数据了,写锁解了,其他线程就可以读数据了)
运行结果:
Thread-0读开始...
Thread-0读数据为:1
Thread-0读结束...
Thread-1写开始...
Thread-1写数据为:0.4387383401358649
Thread-1写结束...
3. 写锁---写锁
相信看了上面两个例子,基本就明白了写锁和写锁是怎么互斥的,同样线程1和线程2都进行写数据,write()方法上写锁并且不解锁(测试才这样写的)
public class ReadWriteLockTest3 {
public static void main(String[] args) {
final ReadAndWrite raw = new ReadAndWrite();
raw.map.put("data", 1);
//线程1,写数据
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.write();
}
}).start();
//线程2,写数据
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.write();
}
}).start();
}
}
class ReadAndWrite{
ReadWriteLock rwlock = new ReentrantReadWriteLock();//读写锁
Map map = new HashMap();//共享的数据
public void write(){
rwlock.writeLock().lock();//上写锁
System.out.println(Thread.currentThread().getName()+"写开始...");
double data = Math.random();
map.put("data", data);
System.out.println(Thread.currentThread().getName()+"写数据为:"+data);
System.out.println(Thread.currentThread().getName()+"写结束...");
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(write()方法里上了写锁并且没有解锁,所以只有一个线程在写数据,其他线程无法进行写数据,你也可以把解锁给加上,运行一下对比看看)运行结果:
Thread-0写开始...
Thread-0写数据为:0.2439106501627466
Thread-0写结束...
Thread-0写开始...
Thread-0写数据为:0.44688857423885386
Thread-0写结束...
Thread-0写开始...
相信通过以上的代码和运行结果进行对比和观察,应该已经理解了ReadWriteLock三个特点,下面贴上实现缓存类的代码(jdk中ReentrantReadWriteLock类的一个例子):
public class Cache {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public Object getData(String key) {
// 首先上读锁
rwLock.readLock().lock();
// 首先从缓存中获取
Object value = null;
try {
Thread.sleep(1000);
value = cache.get(key);
if (value == null) {
// 如果缓存中没有数据,那么就从数据库中获取
// 但此时需要上写锁,只需要让一个进程进行写数据
// 首先去除读锁,然后加上写锁
rwLock.readLock().unlock();
rwLock.writeLock().lock();
try {
// 注意防止多线程运行到上一步,某个线程写完数据后
// 别的线程就需要看是否有数据再决定是否进行写操作
// 在写之前再读一次,防止最开始的线程都进行写操作</span>
value = cache.get(key);
// 第一个线程写完后,防止后面的线程再次写数据
if (value == null) {
System.out.println("有线程写数据........");
value = "数据库中获取";
// 将数据放入缓存
cache.put(key, value);
System.out.println("数据写完了.......");
}
} finally {
rwLock.readLock().lock();// 恢复读锁,锁的重入
rwLock.writeLock().unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();// 解读锁
}
return value;
}
}