在线程池中处理BlockingQueue堵塞队列, 当线程池(ExecutorService)调用shutdown()方法时, 发现线程池未关闭. 错误代码如下:
/**
* Created by sjx on 2020/10/28
*/
public class CustomLinkedBlockingQueue {
private ExecutorService executorService = Executors.newSingleThreadExecutor();
LinkedBlockingQueue<String> queue;
public CustomLinkedBlockingQueue(LinkedBlockingQueue<String> queue) {
this.queue = queue;
looper();
}
public void sendMsg(Object obj) {
if (queue != null) {
if (obj instanceof String) {
queue.add((String) obj);
} else if (obj instanceof List) {
queue.addAll((Collection<? extends String>) obj);
}
}
}
private void looper() {
executorService.execute(new Runnable() {
public void run() {
String task;
while ((task = getTask()) != null) {
System.out.println("Looper : " + task);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
private String getTask() {
String task = null;
try {
task = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return task;
}
public void shutDown() {
executorService.shutdown();
}
}
由于getTask()中, queue.take()方法堵塞. 当调用shutDown()关闭的时候, 会发现关闭不了线程池.
下面简单分析一下线程池部分源码.ThreadPoolExecutor.java, 当线程调起后会调用runWorker(w)方法.
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
由于queue.take()方法堵塞了, 所以会一直堵塞在task.run()这里. 导致w.unlock()无法调用. 当线程池关闭(shutdown())的时候, 会调用interruptIdleWorkers().
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
由于前面w.unlock()无法调用, 导致在
if (!t.isInterrupted() && w.tryLock())
w.tryLock()返回false, 下面无法调用 t.interrupt();
所以无法关闭线程池....
解决方法一:
/**
* Created by sjx on 2020/10/30
*/
public class TestThreadInterrupt implements Runnable {
Thread t = new Thread(this);
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
public TestThreadInterrupt() {
t.start();
}
@Override
public void run() {
String task;
while ((task = getTask()) != null) {
System.out.println("Looper : " + task);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void sendMsg(Object obj) {
if (queue != null) {
if (obj instanceof String) {
queue.add((String) obj);
} else if (obj instanceof List) {
queue.addAll((Collection<? extends String>) obj);
}
}
}
private String getTask() {
String task = null;
try {
task = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return task;
}
public void shutDown() {
t.interrupt();
}
}
自己创建Thread, 不用ExecutorService, 这样控制起来更简单. 当调用 t.interrupt(); queue.take()堵塞被打断, try --- catch--- 返回null . run()方法中, while(task != null), 结束while循环, 当前Thread线程自动关闭.
方法二:
/**
* Created by sjx on 2020/10/30
*/
public class TestExecutorService {
ExecutorService service = Executors.newSingleThreadExecutor();
public void sendMsg(List<String> list) {
if (list != null) {
for (String l : list) {
handleMsg(l);
}
}
}
private void handleMsg(final String msg) {
service.execute(new Runnable() {
public void run() {
System.out.println("msg : " + msg);
}
});
}
public void shutdown() {
service.shutdown();
}
}
由于线程池ExecutorService, 自身就带BlockingQueue队列, execute方法会把Runnable的实现对象放到workQueue中, 通过getTask()获取队列中的数据, 然后再通过 task.run() 方法去执行.
方法三: 写个伪代码吧
private void execute(){
service.execute(new Runnable(){
run(){
String task = getTask();
while(task != null && !"End".equals(task )){
...
}
}
})
}
private String getTask() {
String task = null;
try {
task = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return task;
}
public void showdown(){
queue.offer("End");
service.shutdown();
}
这个很简单, 通过向BlockingQueue添加结束标识位, 当读到这个 "结束标识位" 的时候, 结束 while循环 , shutdown关闭线程池.