多线程并发
1. 线程的创建方式
1.1 继承Thread
public class MyThread extends Thread {
public void run() {
System.out.println("Mythread.run()");
}
}
MyThread t1 = new MyThread();
t1.start();
1.2 实现Runnable
public class MyThread extends OtherClass implements Runnable(){
public void run(){
System.out.println("MyThread.run");
}
}
//启动 MyThread
MyThread t1= new MyThread();
t1.run();
1.3 基于线程池的方式
public class MyThread_1{
public void test(){
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while (true){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName :" + Thread.currentThread().getName());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
1.4 ExecutorService、Callable、Future 有返回值线程
public class MyThread_2{
public void test_2() throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int finalI = i;
Callable c = () -> finalI;
Future f = pool.submit(c);
list.add(f);
}
pool.shutdown();
for (Future f : list) {
System.out.println("res:"+f.get().toString());
}
}
}
1.4.1 Executor VS ExecutorService VS Executors
- ExecutorService 继承了Executor接口,是Executor的子接口
- Executor 定义了 execute() 方法,接收Runnable 接口的对象,没有返回值;ExecutorService 中的 submit() 可以接收 Runnable 和 Callable 接口的对象,通过Future(存储执行的将来结果) 对象返回运算结果
- ExecutorService 可以创建线程池,调用shutDown() 终止线程。
- Executors 类提供工厂方法创建不同类型的线程池。如 newSingleThreadExecutor()
2. ThreadPoolExecutor
ThreadPoolExecutor 继承自 ExecutorService。
2.1 构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
每个参数的含义:
- corePoolSize,核心线程数
- maximumPoolSize,最大线程数
- keepAliveTime,线程数超过核心线程数,空闲时,多长时间被杀死
- unit, 前一个参数的时间单位
- workQueue,任务队列
- threadFactory,线程工厂
- handler,超出线程范围和队列容量是采用的拒绝策略
2.2 拒绝策略
线程池中线程已经用完,无法继续为新任务服务,等待队列也排满了,不能容纳新任务时,需要采用拒绝策略。
JDK提供了一下四种,也可以自定义拒绝策略
拒绝策略 | 描述 |
---|---|
AbortPolicy | 直接抛出异常,阻止系统正常运行 |
CallerRunsPolicy | 若线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务,会影响任务的提交效率 |
DiscardPolicy | 不能执行的任务直接删除 |
DiscardOldPolicy | 若线程执行尚未关闭,最老的线程删除,即工作队列头部的任务删除 |
2.3 线程池工作过程
- 线程池刚创建时,里边没有线程,任务队列是作为参数传进来的。
- 调用execute()方法添加任务时,先启动核心线程,核心线程满了之后,再启动非核心线程,非核心线程满了之后,新任务放入队列中,若队列也满了,采用拒绝策略。
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当线程空闲,且超过keepAliveTime时,若运行线程数大于核心线程数,会停掉线程。线城市的所有任务都完成以后,线程池最终会收缩到核心线程数的大小。
- 线程池底层是通过addworker()方法添加线程的,该方法是通过自旋锁,实现worker数量+1的。
2.4 阻塞队列(BlockingQueue)
阻塞队列 | 描述 |
---|---|
LinkedBlockingQueue | 无界,直到内存满 |
ArrayBlockingQueue | 有界,put()阻塞,add()抛异常,offer()不阻塞 |
DelayQueue | 支持延时获取元素的无界阻塞队列 |
SynchronousQuene | 不存储元素的阻塞队列,只有有take()才put(),类似于传球手,可用于两个线程交换书 |
TransferQueue | 装完元素等着被消费 ,添加了transfer(),等待消费结果,例如:等着支付结果 |
PriorityQueue | 支持优先级的无界队列 ,排好顺序的树,内部采用了二叉树 |
Queue相比较与数组而言,添加了一些对线程友好的api。
- offer(),加值,返回成功与否
- peek(),取值,但不remove
- poll(),取值,并remove
- put(),满了就会等待,阻塞
- take(),取值,为空时等待
3. 线程池种类
线程池种类 | 线程数指定方式 | 使用场景 |
---|---|---|
SingleThreadPool | 一个线程 | 任务队列为LinkedBlockingQueue ,管理线程周期、任务队列 |
CacheThreadPool | 根据需要动态创建新线程 | 线程波动不稳定时使用,短期异步任务, 任务队列为synchronousQueue |
FixThreadPool | 可重用的,固定线程数 | 线程波动稳定时使用 |
SchdualThreadPool1 | 定时任务线程池 | 底层是ThreadPoolExecutor |