首先自定义一个缓存来实现程序的读(get)和写(put)操作。在自定义缓存(这里使用HashMap定义缓存)的时候会使用volatile关键字,volatile关键字的特点可以保证操作的可见性,但不一定保证操作的原子性。为什么要保证操作的可见性,这里简略说一下,因为在多线程情况下如果线程A对缓存进行操作,线程B也对缓存进行操作,在A写入缓存的时候B也写入缓存,会出现B的写入对A的写入覆盖的情况,程序会抛出异常,还有其他的数据异常的情况会出现,详情可以了解java中多线程。
class MyCache1{
private volatile Map<String,Object> map = new HashMap<>();
// 写 ,存
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入start");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入ok");
}
// 读 ,取
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取start");
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取ok");
}
}
通过普通的多线程执行:
// 读写锁
public class test_04 {
public static void main(String[] args) {
MyCache1 cache = new MyCache1(); // 自定义缓存,普通执行
// 写
for (int i = 0; i < 5; i++) {
final int t = i;
new Thread(()->{
cache.put("t = "+ t,t);
},String.valueOf(i)).start();
}
// 读
for (int i = 0; i < 5; i++) {
final int t = i;
new Thread(()->{
cache.get(t+"");
},String.valueOf(i)).start();
}
}
}
执行结果:
我们发现这个写入的读取顺序已经变成了乱序,在线程1写入还没有完成的时候,线程2就已经写入完成,那么线程2的写入已经覆盖了线程1之前的写入,这种多线程操作是不可取的,因此在这个时候我们就要用到读写锁(ReadWriteLock)。
加上读写锁(ReadWriteLock)的多线程程序:
// 加锁的缓存
class MyCache2{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
// 写 ,存 只有一个人可以写
public void put(String key,Object value){
lock.writeLock().lock(); // 写锁加锁
System.out.println(Thread.currentThread().getName()+"写入start");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入ok");
lock.writeLock().unlock();
}
// 读 ,取 所有人都可以读
public void get(String key){
lock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"读取写入start");
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取ok");
lock.readLock().unlock();
}
}
执行结果:
这里注意一下写锁和读锁的区别:
读锁是一种共享锁,所有线程都可以读。
写锁是一种排他锁,即在线程写入的时候,只能有一个线程获得缓存的写入权限,避免多个线程写入对缓存内容进行覆盖。
所以每个线程在写入缓存的时候都独占该缓存,等写入完成释放资源后,其他线程才可以对缓存进行写入,在读的时候所有线程都可以同时读。