背景
接着上一篇《Java多线程系列-增加工作线程-线程池》,Java Concurrent包提供了一些API,怎么用呢;
需求
- 用JUC包提供的互斥、锁机制去改造我们的WebServer;
功能开发
JUC包提供了Lock相关类用来替代synchronized关键字(线程间互斥),提供了Condition来替代wait、notify方法(线程间通信);
我们用Lock、Condition来改造下阻塞队列;加入一个lock-队列本身的锁、一个写线程Condition、一个读线程Condition;如下:
// 共享变量锁
private Lock lock = new ReentrantLock();
// 写线程的条件
private Condition putCondition = lock.newCondition();
// 读线程的条件
private Condition takeCondition = lock.newCondition();
public SimpleQueue(int cap) {
this.capacity = cap;
this.items = new Object[cap];
this.size = 0;
this.putIndex = 0;
this.takeIndex = 0;
}
public void put(E e) throws InterruptedException {
lock.lock();
try {
// 监听器线程往队列中放入socket,如果当前队列满了则监听器等待
while (isFull()) {
Logs.SERVER.info("{} wait put queue : {}", Thread.currentThread().getName(), e);
putCondition.await();
}
// 若队列没满,则监听器线程往队列中放入socket;并且如果先前已经有工作线程在等待取数据,通知工作线程来取
items[putIndex] = e;
putIndex = (putIndex + 1) % capacity;
size++;
Logs.SERVER.info("queue isFull {}, isEmpty {}, capacity {}, size {}, takeIndex {}, putIndex {}", isFull(), isEmpty(),
capacity, size, takeIndex, putIndex);
takeCondition.signalAll();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
// 工作线程来取socket,如果当前队列为空,则工作线程进行等待
lock.lock();
try {
while (isEmpty()) {
Logs.SERVER.info("{} wait get socket", Thread.currentThread().getName());
takeCondition.await();
}
// 队列不为空,工作线程从队列中取出socket;并且如果先前有监听器线程在等待往队列中放数据,通知监听器线程放
E e = (E) items[takeIndex];
// 将已经取走的引用置为空,让GC可以回收
items[takeIndex] = null;
takeIndex = (takeIndex + 1) % capacity;
size--;
Logs.SERVER.info("queue isFull {}, isEmpty {}, capacity {}, size {}, takeIndex {}, putIndex {}", isFull(), isEmpty(),
capacity, size, takeIndex, putIndex);
putCondition.signal();
return e;
} finally {
lock.unlock();
}
}
完整代码实现:WebServer.java
知识点
对于解决线程间互斥、通信两个问题,至此我们用了两种方式,也即Java给出的两套方案,我们的程序逻辑没变,只是换一种方式去实现而已;
其实掌握了其中的思想,剩下的就是查API去实现逻辑,我认为的多线程编程两个主要问题点:互斥、通信,出现这两个问题的关键在于:共享变量,如果没有共享变量,都是线程本地变量,那么就不会存在资源冲突、通信了;打个比方:公司只一台跑步机,所有人都可以用、每个人就是一个线程,而跑步机便是共享变量,A同学在使用跑步机、那个其它同学就只能等待,这就是互斥,如果A用完、告诉B同学你可以使用了,这就叫通信;如果说每人一台跑步机,每个人的跑步机都是自己独享的,就不存在互斥、通信了,因为根本没有竞争;
想明白多线程问题的上下文就不会觉得多线程不好掌握了,其实和我们日常生活中的道理一致,只是苦于一些书籍写的太抽象、不接地气,导致我们理解因难,大道至简;
问题
JUC其实除了Lock、Condition,还给我们提供了很多工具类,比如线程池、Atomic包等,可以极大方便我们平时应用;
当然我们的线程池完全可以用ExcutorService替换掉,我们的队列完全可以也BlockingQueue替换掉;
感想
人生经历的过程的,就是不断认识自己不足、一点点改进的过程;