- 在分析之前我们先学习一个知识点CountDownLatch的使用
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
System.out.println("main start");
new Thread(() -> {
try {
System.out.println("thread1 start");
Thread.sleep(2000);
System.out.println("thread1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}).start();
new Thread(() -> {
try {
System.out.println("thread2 start");
Thread.sleep(3000);
System.out.println("thread2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Optional.ofNullable(countDownLatch).ifPresent(CountDownLatch::countDown);
}
}).start();
countDownLatch.await();
System.out.println("main end");
}
CountDownLatch的作用就是让主线程等待两个子线程执行结束再继续执行
几个关键点方法:
- new CountDownLatch(2)创建倒计时锁存器🔒,倒计时为2
- countDownLatch.await() 等待倒计时结束
- countDownLatch.countDown()计时器减一
BlockingCache关键方法源码解读:
public class BlockingCache implements Cache {
private long timeout;
private final Cache delegate;
// 一条sql查询语句对应一个倒计时锁存器,使用ConcurrentHashMap保证了线程的安全
private final ConcurrentHashMap<Object, CountDownLatch> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
// 线程没有到缓存获取到数据,从数据库获取到之后加入缓存,但是刚才获取的倒计时锁依然没有释放
// 所以这里还要释放倒计时锁
releaseLock(key);
}
}
@Override
public Object getObject(Object key) {
acquireLock(key);
Object value = delegate.getObject(key);
if (value != null) {
// 如果从缓存中获取到了值就释放倒计时锁
releaseLock(key);
}
return value;
}
/**
* 保证了同一时刻只有一个线程能够访问
* @param key
*/
private void acquireLock(Object key) {
CountDownLatch newLatch = new CountDownLatch(1);
// 感觉这里还是没有考虑周全,容易造成死循环
while (true) {
System.out.println(Thread.currentThread().getName() + ":get");
// 如果没有获取到倒计时器,说明put到map成功,获取锁成功
CountDownLatch latch = locks.putIfAbsent(key, newLatch);
if (latch == null) {
System.out.println(Thread.currentThread().getName() + "获取到了锁==========》" + newLatch);
break;
}
try {
// todo 其实这里的超时时间没有用的,因为没有找到设置超时时间的入口
if (timeout > 0) {
// 如果获取到latch,说明已经有线程正在获取,执行超时等待
boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException(
"Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} else {
latch.await();
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
}
}
private void releaseLock(Object key) {
CountDownLatch latch = locks.remove(key);
if (latch == null) {
throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
}
latch.countDown();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}