JUC基础总结06 -线程、线程池
一、Callable接口
二、线程池
1.7大参数
2.4种拒绝策略
3.底层原理
4.使用配置
5.死锁编码定位分析
一、Callable接口
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("callable...");
Thread.sleep(2000);
return 1024;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); //如果是同一个task,即使多个线程,也只进call()一次
System.out.println(Thread.currentThread().getName() + "...");
Integer result = futureTask.get(); //获取callable线程的计算结果,如果没有完成计算,会导致堵塞,直到计算完成
System.out.println("result:" + result);
}
}
main...
callable...
2s后
result:1024
二、线程池
为什么使用线程池?
线程池做的线程复用、控制最大并发数、管理线程
7大参数
- corePoolSize:线程池中的常驻核心线程数
- maximumPoolSize:线程池能够容纳同时执行的最大线程数,此数值必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但尚未被执行的任务
- threadFactory:表示,生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
- handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时,如何来拒绝
4种拒绝策略
- AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
- CallerRunsPolicy:调用者运行,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
- DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案
以上内置策略均实现了RejectedExecutionHandler接口
底层原理
验证从core扩容到maximum后,立即运行当前到达的任务,而不是队列中的
public class T1 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
100,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 1; i <= 8; i++) {
final int tempInt = i;
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "号窗口,服务顾客" + tempInt);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
core=2,所以1号窗口对应1号顾客,2号窗口对应2号顾客,但是接下来,3、4、5号顾客又来了,进入容量为3的队列中排队,接下来6、7、8号顾客又来了,1、2号窗口正在服务,且队列也满了,此时应该开启3、4、5号窗口来提供服务,为6、7、8号顾客提供服务,然后再由这5个窗口为3、4、5号顾客提供服务(即最后执行队列里的任务)
pool-1-thread-1号窗口,服务顾客1
pool-1-thread-3号窗口,服务顾客6
pool-1-thread-4号窗口,服务顾客7
pool-1-thread-5号窗口,服务顾客8
pool-1-thread-2号窗口,服务顾客2
3s后,
pool-1-thread-4号窗口,服务顾客3
pool-1-thread-5号窗口,服务顾客4
pool-1-thread-2号窗口,服务顾客5
使用配置
CPU密集型 线程池线程数量 = CPU数量+1
IO密集型 意味着大量的IO,大部分线程都会阻塞,所以需要配置更多线程,参考公式 CPU核数/(1-阻塞系数),当阻塞系数为0.8~0.9之间,线程数≈10*CPU数量
注:CPU核心数=RunTime.getRuntime().availableProcessors(); //动态获取当前机器的核心数
死锁编码定位分析
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
死锁demo代码
public class MyDeadLockDemo {
public static Object lockA = new Object();
public static Object lockB = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "\t持有" + lockA + ",等待" +lockB);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println("线程A完成");
}
}
}, "A").start();
new Thread(() -> {
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "\t持有" + lockB + ",等待" +lockA);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA) {
System.out.println("线程B完成");
}
}
}, "B").start();
}
}
定位和解决
jps命令定位进程号 jps -l
jstack找到死锁查看 jstack 进程号
补充:Executors创建的线程池对象弊端如下
FixedThreadPool和SingleThreadPool允许的请求队列长度最大为Integer.MAX_VALUE,可能会堆积大量的请求导致OOM
CachedThreadPool和ScheduledThreadPool允许创建线程数最大为Integer.MAX_VALUE,可能会创建大量的线程导致OOM