一、阻塞队列
- 队列的特点:先进先出
- 底层数据结构:循环队列,链表
- jdk队列实现:可以看Queue接口实现类
- 阻塞队列:BlockingQueue接口,使用循环数组实现队列(阻塞队列的实现需要保证线程安全[take和 put])
实现:
package lesson;
/**
* 实现阻塞队列:
* 1. 线程安全问题:在多线程下,put、take不具有原子性,4个属性,不具有可见性
* 2. put操作,如果存满了,需要阻塞等待;take如果是空,需要阻塞等待
* @param <T>
*/
public class MyBlockIngQueue1<T> {
private Object[] queue; //使用数组实现循环队列
private int putIndex; //存放元素的索引
private int takeIndex; //取元素的索引
private int size; //当前存放元素的数量
public MyBlockIngQueue1(int len) {
queue = new Object[len];
}
//存放元素,
// 需要考虑 1.putIndex超过数组的长度 2.size打到数组最大长度
public synchronized void put(T e) throws InterruptedException { //synchronized保证线程安全
//当阻塞等待,到被唤醒并再次竞争成功对象锁,亏负后往下继续执行时
//条件可能会被其他线程修改 使用while
while (size == queue.length) { //放满了开始阻塞等待
this.wait(); //或者wait()
//throw new RuntimeException("超过最大长度");
}
//存放到数组中放元素的位置
queue[putIndex] = e;
//存放位置超过数组的最大索引,需要取模操作,将其放在0位置
putIndex = (putIndex + 1) % queue.length;
size++;
this.notifyAll(); //notifyAll() 和synchronized加锁对象一样
}
//取元素
public synchronized T take() throws InterruptedException {
while (size == 0) {
wait();
//throw new RuntimeException("已经没有存放元素了");
}
T t = (T) queue[takeIndex];
queue[takeIndex] = null; //取出来后将该数组的内容置为null
takeIndex = (takeIndex + 1) % queue.length;
size--;
notifyAll(); //唤醒所有线程
return t;
}
//
public synchronized int size() {
return size;
}
public static void main(String[] args) {
MyBlockIngQueue1<Integer> queue1 = new MyBlockIngQueue1<>(10);
//多线程的调试方式:1.打印语句
// 2.jconsole/jstack
// 3.debug在有些场景不一定适用
for (int i = 0; i < 3;i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int j = 0; j < 100;j++) {
queue1.put(j);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
for (int i = 0; i < 3;i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
for (;;) {
int i = queue1.take();
System.out.println(Thread.currentThread().getName() + ":" +i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
// MyBlockIngQueue1<String> queue1 = new MyBlockIngQueue1<>(10);
// for (int i = 0; i < 10;i++) {
// queue1.put(String.valueOf(i));
// }
queue1.put("A");
queue1.put("B");
queue1.put("C");
// queue1.take();
// queue1.put("10");
// for (Object o : queue1.queue) {
// System.out.println(o);
// }
}
}
多线程的调试方式:
- 打印语句
- jconsole/jstack
- debug在有些场景不一定适用
以下下是使用 jconsole 实现多线程的调试步骤:
只放元素时:
存在取元素时:
二、线程池
背景:new Thread操作非常耗时
例如:快递公司的快递任务
线程池:
线程池最大的好处就是减少每次启动、销毁线程的损耗
线程池的4个快捷创建方式:
- ExecutorService pool1 = Executors.newSingleThreadExecutor();//单线程池
- ExecutorService pool2 = Executors.newCachedThreadPool();// 缓存的线程池
- ExecutorService pool3 = Executors.newScheduledThreadPool(4); //计划任务线程池
- ExecutorService pool4 = Executors.newFixedThreadPool(4); //固定大小的线程池
但是阿里Java开发手册中说明不能直接使用以下方式创建线程池(需要指定任务阻塞队列,拒绝策略,线程工厂)
建议使用ThreadPoolExecutor创建线程池,传入参数自己指定(各参数的含义)
线程池的使用场景/优点:对比new Thread,创建、销毁线程都比较耗时,使用线程可以达到线程重用、复用。-----》池化:享元模式,如连接池、常量池
执行流程:execute()和submit()
关闭线程池:shutdown或者shutdownNow
- shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
例:threadPoolExecutor.shutdown();- shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并 返回等待执行任务的列表(对任务队列及线程里的都中断)
代码:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
//线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor( //使用ThreadPoolExecutor创建线程池
5, //核心线程数 -->正式员工
10, //最大线程数 --->正式员工 + 临时工
60,
TimeUnit.SECONDS, //idle线程临时的空闲时间 --->临时工最大的存活时间,超过时间就解雇
//ThreadFactory threadFactory //临时工厂
new LinkedBlockingQueue<>(), //阻塞队列:任务存放的地方--->快递仓库
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
//return new Thread(r);
//打印
return new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行了");
//r对象是线程池内部封装过的工作任务类(Worker),会一直循环等待的方式从阻塞队列中取任务来执行
// r.run();
}
});
}
}, //创建线程的工厂类:线程池创建线程时,调用该工厂的方法来创建线程 --->招聘员工的标准
new ThreadPoolExecutor.AbortPolicy()
/**
* 拒绝策略:达到最大线程数且阻塞队列已满采取的拒绝策略
* 1.AbortPolicy():直接抛RejectExecutionException (不提供handLer时的默认策略)
* 2.CallerRunsPolicy():谁(某个线程)交给我(线程池)任务,我(线程池)拒绝执行,由谁(某个线程)自己执行
* 3.DiscardPolicy():交给我的任务直接丢弃掉
* 4.DiscardOldestPolicy():丢弃阻塞队列中最旧的任务
*/
); //线程池创建以后,只要有任务就自动执行
for (int i = 0; i < 20;i++) {
final int j = i;
/**
* 线程池执行任务:
* 1.execute 2.submit --》提交执行一个任务
*/
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(j);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
ExecutorService pool1 = Executors.newSingleThreadExecutor();//单线程池
ExecutorService pool2 = Executors.newCachedThreadPool();// 缓存的线程池
ScheduledExecutorService pool3 = Executors.newScheduledThreadPool(4); //计划任务线程池
ExecutorService pool4 = Executors.newFixedThreadPool(4); //固定大小的线程池
// pool3.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println("hhh");
// }
// },2,TimeUnit.SECONDS);
pool3.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("hehe");
}
},2,1,TimeUnit.SECONDS); //间隔两秒之后执行,之后每间隔一秒执行一次
}
}
}