首先队列是一种特殊的线性表,它只能够在表的后端(rear)进行数据插入,在表的前端(front)进行数据获取;和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
支持阻塞的含义:
BlockingQueue
该类是阻塞队列的父类,他是一个接口类,其余的阻塞队列都需要继承它。
BlockingQueue
中并非只有阻塞方法,也有非阻塞的方法:
IllegalStateException("Queuefull")
异常。当队列空时,从队列里获取元素会抛出NoSuchElementException
异常。put()
元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take()
元素,队列会阻塞住消费者线程,直到队列不为空。常用的阻塞队列有以下几种,都是继承了BlockingQueue
接口类的:
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
compareTo()
方法来指定元素排序规则,或者初始化PriorityBlockingQueue
时,指定构造参数Comparator
来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。DelayQueue
PriorityQueue
来实现。队列中的元素必须实现Delayed
接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。DelayQueue
可以用作缓存系统的设计:可以用DelayQueue
保存缓存元素的有效期,使用一个线程循环查询DelayQueue
,一旦能从DelayQueue
中获取元素时,表示缓存有效期到了。SynchronousQueue
put
操作必须等待一个take
操作,否则不能继续添加元素。SynchronousQueue
可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。SynchronousQueue
的吞吐量高于LinkedBlockingQueue
和ArrayBlockingQueue
。LinkedTransferQueue
tryTransfer
和transfer
两个方法
transfer()
take()
方法或带时间限制的poll()
方法时),transfer()
方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer()
方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。tryTransfer()
tryTransfer()
方法是用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer()
方法的区别是tryTransfer()
方法无论消费者是否接收,方法立即返回,而transfer()
方法是必须等到消费者消费了才返回。LinkedBlockingDeque
addFirst()
、addLast()
、offerFirst()
、offerLast()
、peekFirst()
和peekLast()
等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。另外,插入方法add()
等同于addLast()
,移除方法remove()
等效于removeFirst()
。但是take()
方法却等同于takeFirst()
,不知道是不是JDK的bug,使用时还是用带有First和Last后缀的方法更清楚。在初始化LinkedBlockingDeque
时可以设置容量防止其过度膨胀。另外,双向阻塞队列可以运用在工作窃取模式中。ArrayBlockingQueue
和LinkedBlockingQueue
实现的区别
ArrayBlockingQueue
实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;LinkedBlockingQueue
实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLockArrayBlockingQueue
实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;LinkedBlockingQueue
实现的队列中在生产和消费的时候,需要把枚举对象转换为Node
进行插入或移除,会影响性能ArrayBlockingQueue
实现的队列中必须指定队列的大小;LinkedBlockingQueue
实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE/**
* 实现一个固定时间后获取数据输出的生产者、消费者模型
*/
/**
* 实际存放的数据类
*/
class Order {
private int price;
private String name;
public Order(int price, String name) {
this.name = name;
this.price = price;
}
public int getPrice() {
return price;
}
public String getName() {
return name;
}
}
/**
* 存放元素的封装
* 该封装是需要存放进 DelayQueue中的,而查看DelayQueue
* 其中的元素是需要继承Delayed类的
*/
class ItemVo<T> implements Delayed {
private long responseTime; //延迟时间
private T data; //真实的存储数据
public ItemVo(long delayTime, T d) {
responseTime = delayTime*1000 + System.currentTimeMillis();
data = d;
}
public long getResponseTime() {
return responseTime;
}
public T getData() {
return data;
}
//获取当前剩余时间
@Override
public long getDelay(TimeUnit timeUnit) {
long remainingTime = timeUnit.convert(responseTime - System.currentTimeMillis(),
timeUnit);
return remainingTime;
}
//比较不同任务之间的剩余时间大小
@Override
public int compareTo(Delayed delayed) {
long t = this.getDelay(TimeUnit.MILLISECONDS) - delayed.getDelay(TimeUnit.MILLISECONDS);
if (t == 0) {
return 0;
} else if (t < 0) {
return -1;
} else {
return 1;
}
}
}
import java.util.concurrent.DelayQueue;
//生产者线程
class PutOrder implements Runnable {
public DelayQueue<ItemVo<Order>> queue;
public PutOrder(DelayQueue<ItemVo<Order>> q) {
queue = q;
}
/**
* 生产者线程负责将数放入到DelayQueue中,以供消费者去获取
*/
@Override
public void run() {
Order order3 = new Order(100, "中美关系");
ItemVo<Order> item3 = new ItemVo<>(10, order3);
queue.offer(item3);
Order order1 = new Order(55, "三体");
ItemVo<Order> item1 = new ItemVo<>(5, order1);
queue.offer(item1);
Order order2 = new Order(88, "1740");
ItemVo<Order> item2 = new ItemVo<>(8, order2);
queue.offer(item2);
System.out.println("数据存放完毕");
}
}
//消费者线程
class FetchOrder implements Runnable {
public DelayQueue<ItemVo<Order>> queue;
public FetchOrder(DelayQueue<ItemVo<Order>> q) {
queue = q;
}
/**
* 消费者线程从DelayQueue中获取元素(因为DelayQueue本身就是按优先级排序的阻塞队列,
* 所以获取的队列元素一定是延时最短的元素),DelayQueue本身是阻塞队列,当使用take()
* 这个阻塞方法能够获取元素时,表示条件成立,即获取时间到达,否则会一直阻塞,无法获取到元素
*/
@Override
public void run() {
System.out.println("enter FetchOrder run!");
while (true) {
try {
ItemVo<Order> itemVo = queue.take();
Order order = (Order)itemVo.getData();
System.out.println("Order name = " + order.getName() +
", price = " + order.getPrice() + ", this time = " + System.currentTimeMillis()
+ ", itemVo time = " + itemVo.getResponseTime());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Test {
//使用DelayQuene实现存储元素特定时间后被获取的功能
public static void main(String[] args) throws InterruptedException {
//首先创建一个queue
DelayQueue<ItemVo<Order>> queue = new DelayQueue<ItemVo<Order>>();
//构建出生产者和消费者线程
new Thread(new PutOrder(queue)).start();
new Thread(new FetchOrder(queue)).start();
//主线程做计时操作
for (int i = 0; i < 100; i++) {
Thread.sleep(500);
System.out.println(i*500);
}
}
}
线程池简单化来说,即事先就构建出一定数量的工作线程,当有任务的时候就提交任务给构建好的工作线程去执行,当任务数超过总的工作线程时则将任务暂时存放在任务队列中,等待有空闲的工作线程时,再去执行任务;根据上述方式,我们可以自定义一个线程池:
//自定义的线程池
class MyThreadPool {
//最大线程数
public int thread_max;
//任务队列
public BlockingQueue<Runnable> taskQueue;
//工作线程存放数组
public WorkThread[] threads;
//默认值
public static final int THREAD_MAX = 3;
public static final int TASK_MAX = 5;
public MyThreadPool() {
this(3, 5);
}
public MyThreadPool(int thread_num, int task_num) {
if (thread_num <= 0) {
this.thread_max = THREAD_MAX;
} else {
this.thread_max = thread_num;
}
if (task_num <= 0) {
task_num = TASK_MAX;
}
//构建任务队列,任务队列的创建要在工作线程开启之前,否则就会抛出异常
taskQueue = new ArrayBlockingQueue<Runnable>(task_num);
//构建对应数量的工作线程
threads = new WorkThread[thread_max];
for (int i = 0; i < thread_num; i++) {
threads[i] = new WorkThread();
threads[i].start();
}
}
public void execute(Runnable task) throws InterruptedException {
taskQueue.put(task);
}
public void destroy() {
System.out.println("now will stop all work Thread......");
for (int i = 0; i < this.thread_max; i++) {
threads[i].stopWorkThread();
threads[i] = null;
}
taskQueue.clear();
}
class WorkThread extends Thread {
@Override
public void run() {
Runnable runnable = null;
try {
//终止工作线程的条件
while (!isInterrupted()) {
if (taskQueue != null) {
runnable = taskQueue.take();
if (runnable != null) {
System.out.println(getId() + ", will start runable : " + runnable);
runnable.run();
}
}
runnable = null;
}
} catch (Exception e) {
}
}
//用来停止工作线程
public void stopWorkThread() {
interrupt();
}
}
}
//使用自定义的线程池完成工作
class UseMyThreadPool {
// 任务类
static class MyTask implements Runnable {
private String name;
private Random r = new Random();
public MyTask(String name) {
this.name = name;
}
// 执行任务
@Override
public void run() {
try {
Thread.sleep(r.nextInt(100)+200);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getId()+" sleep InterruptedException:"
+Thread.currentThread().isInterrupted());
}
System.out.println("任务 " + name + " 完成");
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadPool threadPool = new MyThreadPool();
threadPool.execute(new MyTask("11111"));
threadPool.execute(new MyTask("22222"));
threadPool.execute(new MyTask("33333"));
threadPool.execute(new MyTask("44444"));
threadPool.execute(new MyTask("55555"));
threadPool.execute(new MyTask("66666"));
Thread.sleep(50000);
threadPool.destroy();
}
}
关于自定义的线程池,最核心的内容就是:
Executor
Executor
框架的基础,他将任务的提交和任务的执行分离开来void execute(Runnable var1);
ExecutorService
ExecutorService
接口继承了Executor
,并在这个基础上做了方法拓展,比如shutdown
、shutdownNow
、submit
方法等,该接口类可以说是真正的线程池的接口AbstractExecutorService
AbstractExecutorService
抽象类实现了ExecutorService
接口中的大部分方法ThreadPoolExecutor
ThreadPoolExecutor
是线程池的核心实现类,继承了AbstractExecutorService
,它是用来执行被提交的任务的ScheduledExecutorService
ScheduledExecutorService
接口继承了ExecutorService
,提供了带周期执行功能的ExecutorService
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令ThreadPoolExecutor
构造函数中各个参数的意义public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
prestartAllCoreThreads()
方法,线程池会提前创建并启动所有核心线程。maximumPoolSize
keepAliveTime
corePoolSize
时才有用,即假设核心线程数是5,最大线程数是10,那么当时间到后,只会杀掉超过核心线程数的5个线程unit
keepAliveTime
的时间单位workQueue
corePoolSize
的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue
,线程池实现了阻塞功能threadFactory
Executors
静态工厂里默认的threadFactory
,线程的命名规则是pool-数字-thread-数字。handler
AbortPolicy
:直接抛出异常,默认策略CallerRunsPolicy
:用调用者所在的线程来执行任务DiscardOldestPolicy
:丢弃阻塞队列中靠最前的任务,并执行当前任务DiscardPolicy
:直接丢弃任务RejectedExecutionHandler
接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务ThreadPoolExecutor
的工作流程解析线程池的流程:
workQueue
为什么需要尽量使用有界队列
corePoolSize
后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize
。maximumPoolSize
将是一个无效参数。keepAliveTime
将是一个无效参数。使用线程池提交任务有两个方法
public void execute(Runnable command)
execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。public
public
public
submit()
方法用于提交需要返回值的任务get()
方法来获取返回值get()
方法会阻塞当前线程直到任务完成;而使用get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。public void shutdown()
public List shutdownNow()
shutdownNow()
方法会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表shutdown()
方法只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程public boolean isShutdown()
isShutdown()
方法就会返回TRUEpublic boolean isTerminated()
isTerminated()
方法才会返回TRUEmaximumPoolSize
)
Runtime.getRuntime().availableProcessors()
方法获得当前设备的CPU个数PriorityBlockingQueue
来处理。它可以让优先级高的任务先执行class UseThreadPool {
static class callWork implements Runnable {
public String name;
public callWork(String str) {
name = str;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "," + this.name);
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = new ThreadPoolExecutor(1, 4, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i < 20; i++) {
callWork callWorker = new callWork("callWork-" + i);
pool.execute(callWorker);
}
pool.shutdown();
}
}