ArrayBlockingQueue NoSuchElementException 异常
ArrayBlockingQueue NoSuchElementException
为了学习,我为自定义线程池编写了以下代码,参考并编辑显示的代码here.
如代码所示,我将 ArrayBlockingQueue 用于任务队列。
代码:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class ThreadPoolService {
private final BlockingQueue<Runnable> taskQueue;
private final int corePoolSize;
private ThreadPoolService(int corePoolSize) {
this.corePoolSize = corePoolSize;
this.taskQueue = new ArrayBlockingQueue<>(corePoolSize);
ThreadPool[] threadPool = new ThreadPool[corePoolSize];
for (int i = 0; i < corePoolSize; i++) {
threadPool[i] = new ThreadPool();
threadPool[i].start();
}
}
public static ThreadPoolService newFixedThreadPool(int size) {
return new ThreadPoolService(size);
}
public void execute(Runnable task) {
try {
taskQueue.offer(task, 10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class ThreadPool extends Thread {
Runnable task;
@Override
public void run() {
while (true) {
try {
while (!taskQueue.isEmpty()) {
task = taskQueue.remove();
task.run();
}
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ThreadPoolService pool = ThreadPoolService.newFixedThreadPool(10);
Runnable task1 = () -> {
System.out.println(" Wait for sometime: -> " + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable task2 = () -> System.out.println(" Do Task 2 -> " + Thread.currentThread().getName());
Runnable task3 = () -> System.out.println(" Do Task 3 -> " + Thread.currentThread().getName());
Runnable task4 = () -> System.out.println(" Do Task 4 -> " + Thread.currentThread().getName());
List<Runnable> taskList = new ArrayList<>();
taskList.add(task1);
taskList.add(task2);
taskList.add(task3);
taskList.add(task4);
for (Runnable task : taskList) {
pool.execute(task);
}
}
}
此代码有时运行良好,有时会出错。
成功输出:
Do Task 2 -> Thread-2
Wait for sometime: -> Thread-8
Do Task 3 -> Thread-6
Do Task 4 -> Thread-7
失败输出:
Do Task 4 -> Thread-3
Do Task 3 -> Thread-6
Wait for sometime: -> Thread-4
Do Task 2 -> Thread-7
java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
我看到错误的原因是在队列为空时尝试删除元素。但这不应该,因为我正在第 42 行 (while (!taskQueue.isEmpty())
) 进行队列空检查。代码有什么问题,以及为什么它有时可以正常运行?
在 'while' 检查和实际删除之间,队列可能被另一个线程修改,可能导致您提到的错误。这叫做 'race condition'.
因此,为了解决这个问题,您需要一种方法来阻止其他线程对队列的访问,或者通过 'locking',使用带有共享锁对象的 'synchronized' 块。或者简单地通过 'polling' 而不是删除。
BlockingQueue
仅在单个操作上是线程安全的 level.I我在代码中看到 check-then-act
操作是一个非线程安全的复合操作。要使此代码线程安全,请在同步块内执行 check-then-act
并锁定队列本身。
synchronized(taskQueue) {
while (!taskQueue.isEmpty()) {
task = taskQueue.remove();
task.run();
}};
优化:如果是比较耗时的任务,可以在synchronized块外执行。这样其他线程就不必等到当前任务完成。
What is wrong with code?
您从多个线程访问 taskQueue
字段而不同步。您必须以原子方式执行队列空检查和删除操作,这可以使用 synchronized
关键字来完成:
private class ThreadPool extends Thread {
@Override
public void run() {
Runnable task;
while (true) {
synchronized(queue) {
// give access to taskQueue to one thread at a time
if (!taskQueue.isEmpty()) {
task = taskQueue.remove();
}
}
try {
task.run();
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
}
}
why it runs without error sometimes?
由于 JVM 线程调度程序的性质:有时它会以线程自己同步访问 taskQueue
的方式来计划线程执行。但是在处理多线程的时候,不能依赖线程的执行顺序,必须自己同步访问共享对象。
为了学习,我为自定义线程池编写了以下代码,参考并编辑显示的代码here.
如代码所示,我将 ArrayBlockingQueue 用于任务队列。
代码:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class ThreadPoolService {
private final BlockingQueue<Runnable> taskQueue;
private final int corePoolSize;
private ThreadPoolService(int corePoolSize) {
this.corePoolSize = corePoolSize;
this.taskQueue = new ArrayBlockingQueue<>(corePoolSize);
ThreadPool[] threadPool = new ThreadPool[corePoolSize];
for (int i = 0; i < corePoolSize; i++) {
threadPool[i] = new ThreadPool();
threadPool[i].start();
}
}
public static ThreadPoolService newFixedThreadPool(int size) {
return new ThreadPoolService(size);
}
public void execute(Runnable task) {
try {
taskQueue.offer(task, 10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class ThreadPool extends Thread {
Runnable task;
@Override
public void run() {
while (true) {
try {
while (!taskQueue.isEmpty()) {
task = taskQueue.remove();
task.run();
}
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ThreadPoolService pool = ThreadPoolService.newFixedThreadPool(10);
Runnable task1 = () -> {
System.out.println(" Wait for sometime: -> " + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable task2 = () -> System.out.println(" Do Task 2 -> " + Thread.currentThread().getName());
Runnable task3 = () -> System.out.println(" Do Task 3 -> " + Thread.currentThread().getName());
Runnable task4 = () -> System.out.println(" Do Task 4 -> " + Thread.currentThread().getName());
List<Runnable> taskList = new ArrayList<>();
taskList.add(task1);
taskList.add(task2);
taskList.add(task3);
taskList.add(task4);
for (Runnable task : taskList) {
pool.execute(task);
}
}
}
此代码有时运行良好,有时会出错。
成功输出:
Do Task 2 -> Thread-2
Wait for sometime: -> Thread-8
Do Task 3 -> Thread-6
Do Task 4 -> Thread-7
失败输出:
Do Task 4 -> Thread-3
Do Task 3 -> Thread-6
Wait for sometime: -> Thread-4
Do Task 2 -> Thread-7
java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.interview.java.ThreadPoolService$ThreadPool.run(ThreadPoolService.java:43)
我看到错误的原因是在队列为空时尝试删除元素。但这不应该,因为我正在第 42 行 (while (!taskQueue.isEmpty())
) 进行队列空检查。代码有什么问题,以及为什么它有时可以正常运行?
在 'while' 检查和实际删除之间,队列可能被另一个线程修改,可能导致您提到的错误。这叫做 'race condition'.
因此,为了解决这个问题,您需要一种方法来阻止其他线程对队列的访问,或者通过 'locking',使用带有共享锁对象的 'synchronized' 块。或者简单地通过 'polling' 而不是删除。
BlockingQueue
仅在单个操作上是线程安全的 level.I我在代码中看到 check-then-act
操作是一个非线程安全的复合操作。要使此代码线程安全,请在同步块内执行 check-then-act
并锁定队列本身。
synchronized(taskQueue) {
while (!taskQueue.isEmpty()) {
task = taskQueue.remove();
task.run();
}};
优化:如果是比较耗时的任务,可以在synchronized块外执行。这样其他线程就不必等到当前任务完成。
What is wrong with code?
您从多个线程访问 taskQueue
字段而不同步。您必须以原子方式执行队列空检查和删除操作,这可以使用 synchronized
关键字来完成:
private class ThreadPool extends Thread {
@Override
public void run() {
Runnable task;
while (true) {
synchronized(queue) {
// give access to taskQueue to one thread at a time
if (!taskQueue.isEmpty()) {
task = taskQueue.remove();
}
}
try {
task.run();
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
}
}
why it runs without error sometimes?
由于 JVM 线程调度程序的性质:有时它会以线程自己同步访问 taskQueue
的方式来计划线程执行。但是在处理多线程的时候,不能依赖线程的执行顺序,必须自己同步访问共享对象。