Java线程池及Thread相关问题
- 一、Java线程池有哪些核心参数,分别有什么的作用?
- 二、线程池有哪些拒绝策略?
- 三、线程池的执行流程?
- 四、线程池核心线程数怎么设置呢?
- 方式一
- 方式二
- 基本原则
- 五、ThreadLocal底层是怎么实现的?
- 六、ThreadLocal为什么会内存泄漏?
- 七、sleep()和wait()有什么区别?
- 八、多个线程如何保证按顺序执行?
- 九、Java线程池中submit()和execute()方法有什么区别?
一、Java线程池有哪些核心参数,分别有什么的作用?
构造方法最多的是7个参数;
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 8, 16, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1024), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() );
public static void main(String[] args) {//基于Executor框架实现线程池ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8,16,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(1024),Executors.defaultThreadFactory(),//new MyThreadFactory("my-thread-pool-"),new ThreadPoolExecutor.AbortPolicy());//创建线程池后,初始化一个核心线程//threadPoolExecutor.prestartCoreThread();//创建线程池后,初始化所有的核心线程//threadPoolExecutor.prestartAllCoreThreads();//设置核心线程在空闲超时后是否允许销毁,true表示允许销毁threadPoolExecutor.allowCoreThreadTimeOut(true);threadPoolExecutor.execute(() -> {System.out.println("任务执行......" + Thread.currentThread().getName());});threadPoolExecutor.shutdown();}static class MyThreadFactory implements ThreadFactory {private final AtomicInteger threadNumber = new AtomicInteger(1); //线程编号private final String namePrefix; //线程名称的前缀public MyThreadFactory(String namePrefix) {this.namePrefix = namePrefix;}@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());if (t.isDaemon())t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;}}
- int corePoolSize
线程池中的核心线程数量
allowCoreThreadTimeOut;允许核心线程超时销毁;
boolean prestartCoreThread(),初始化一个核心线程;
int prestartAllCoreThreads(),初始化所有核心线程;
- int maximumPoolSize
线程池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize
这个上限;
- long keepAliveTime
线程空闲超时时间,如果一个线程处于空闲状态,并且当前的线程数量大于corePoolSize
,那么在指定时间后,这个空闲线程会被销毁;
- TimeUnit unit
keepAliveTime
的时间单位 (天、小时、分、秒…);
- BlockingQueue<Runnable> workQueue
任务队列,当核心线程全部繁忙时,任务存放到该任务队列中,等待被核心线程来执行;
- ThreadFactory threadFactory
线程工厂,用于创建线程,一般采用默认的线程工厂即可,也可以自定义实现;
Executors.defaultThreadFactory(),
Executors.privilegedThreadFactory(),
- RejectedExecutionHandler handler
拒绝策略(饱和策略),当任务太多来不及处理时,如何 “拒绝” 任务?
1、核心线程corePoolSize
正在执行任务;
2、线程池的任务队列workQueue
已满;
3、线程池中的线程数达到maximumPoolSize
时;
就需要 “拒绝” 掉新提交过来的任务;
二、线程池有哪些拒绝策略?
JDK提供了4种内置的拒绝策略:AbortPolicy
、CallerRunsPolicy
、DiscardOldestPolicy
和DiscardPolicy
;
- 1、AbortPolicy(默认):丢弃任务并抛出
RejectedExecutionException
异常,这是默认的拒绝策略; - 2、DiscardPolicy:直接丢弃任务,不抛出异常,没有任何提示;
- 3、DiscardOldestPolicy:丢弃任务队列中靠最前的任务,当前提交的任务不会丢弃;
- 4、CallerRunsPolicy: 交由任务的调用线程(提交任务的线程)来执行当前任务;
- 除了上面的四种拒绝策略,还可以通过实现
RejectedExecutionHandler
接口,实现自定义的拒绝策略;
三、线程池的执行流程?
当提交一个新任务到线程池时,具体的执行流程如下:
- 当我们提交任务,线程池会根据
corePoolSize
大小创建线程来执行任务; - 当任务的数量超过
corePoolSize
数量,后续的任务将会进入阻塞队列阻塞排队; - 当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize个额外创建的线程等待
keepAliveTime
之后被自动销毁; - 如果达到
maximumPoolSize
,阻塞队列还是满的状态,那么将根据不同的拒绝策略进行拒绝处理;
四、线程池核心线程数怎么设置呢?
方式一
Ncpu = cpu的核心数 ,Ucpu = cpu的使用率(在0~1之间),W = 线程等待时间,C = 线程计算时间
举例:
- 8 * 100% * (1+60/40) = 20
- 8 * 100% * (1+80/20) = 40
方式二
任务分为CPU密集型和IO密集型
- CPU密集型
线程数 = CPU核心数 + 1;
- 这种任务主要是消耗CPU资源, 比如像加解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务;
- +1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间;
- IO密集型
线程数 = CPU核心数 * 2;
- 这种任务会有大部分时间在进行 IO 操作,比如像 MySQL 数据库、文件读写、网络通信等任务,这类任务不会特别消耗CPU资源,但是 IO 操作比较耗时,会占用比较多时间;
- 线程在处理 IO 的时间段内不会占用 CPU,这时就可以将 CPU 交出给其它线程使用,因此在 IO 密集型任务的应用中,可以多配置一些线程;
基本原则
- 1、线程执行时间越多,就需要越少的线程;
- 2、线程等待时间越多,就需要越多的线程;
- 以上理论参考依据,实际项目中建议在本地或者测试环境进行压测多次调整线程池大小,找到相对理想的值大小;
五、ThreadLocal底层是怎么实现的?
六、ThreadLocal为什么会内存泄漏?
-
ThreadLocal是一个类似于HashMap的数据结构;
-
ThreadLocal的实现原理就是通过set把value set到线程的
threadlocals
属性中,threadlocals
是一个Map,其中的key是ThreadLocal的this引用,value是我们所set的值;
在使用线程池的时候,可能由于线程没有结束,ThreadLocalMap对象的 Entry[] 数组中的数据没有被回收,内存没有释放,那么当线程执行大量数据时,可能导致内存溢出,数据泄露。
七、sleep()和wait()有什么区别?
(1) 所属类不同;
- sleep 是Thread的;
- wait 是Object的;
Thread.sleep(1000);Object object = new Object();
object.wait();
(2) 方法类型不用;
- sleep 是静态方法;
- wait 是实例方法;
(3) 使用语法不同;
- sleep 可以直接使用
- wait 获取锁之后,才可以使用,不然会报错;
(4) 唤醒方式不同;
- sleep 设置时间过期后自动唤醒;
- wait 可以设置过期时间,也可以使用notify()或notifyAll()方法唤醒;
(5) 释放锁资源不同;
- sleep 自动释放锁;
- wait 不会自动释放锁;
(6) 线程状态不同
- sleep 状态:
TIMED_WAITING
- wait 状态:
WAITING
八、多个线程如何保证按顺序执行?
比如任务B,它需要等待任务A执行之后才能执行,任务C需要等待任务B执行之后才能执行;
1、通过join()
方法使当前线程 “阻塞”,等待指定线程执行完毕后继续执行;
public static void main(String[] args) throws Exception {// t1线程Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 执行");}, "t1");// t2线程Thread t2 = new Thread(() -> {try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 执行");}, "t2");// t3线程Thread t3 = new Thread(() -> {try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 执行");}, "t3");t1.start();t2.start();t3.start();}
或者:
public static void main(String[] args) throws Exception {// t1线程Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 执行");}, "t1");// t2线程Thread t2 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 执行");}, "t2");// t3线程Thread t3 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 执行");}, "t3");t1.start();t1.join();t2.start();t2.join();t3.start();}
2、通过创建单一化线程池newSingleThreadExecutor()
实现;
// 自定义线程池private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(1024),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());// 线上环境不推荐使用这种方式(阿里巴巴编码规范不推荐使用)private static ExecutorService executorService = Executors.newSingleThreadExecutor();public static void main(String[] args) throws Exception {// t1线程Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " t1执行");}, "t1");// t2线程Thread t2 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " t2执行");}, "t2");// t3线程Thread t3 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " t3执行");}, "t3");threadPoolExecutor.execute(t1);threadPoolExecutor.execute(t2);threadPoolExecutor.execute(t3);threadPoolExecutor.shutdown();}
3、通过倒数计时器CountDownLatch
实现;
/** 用于判断t1线程是否执行,倒计时设置为1,执行后减1 */private static final CountDownLatch countDownLatch1 = new CountDownLatch(1);/** 用于判断t2线程是否执行,倒计时设置为1,执行后减1 */private static final CountDownLatch countDownLatch2 = new CountDownLatch(1);public static void main(String[] args) throws Exception {// t1线程Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 执行");countDownLatch1.countDown(); // -1}, "t1");// t2线程Thread t2 = new Thread(() -> {try {countDownLatch1.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 执行");countDownLatch2.countDown();}, "t2");// t3线程Thread t3 = new Thread(() -> {try {countDownLatch2.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 执行");}, "t3");t1.start();t2.start();t3.start();}
4、使用Object的wait/notify
方法实现;
5、使用线程的Condition(条件变量)
方法实现;
6、使用线程的CyclicBarrier(回环栅栏)
方法实现;
7、使用线程的Semaphore(信号量)
方法实现;
九、Java线程池中submit()和execute()方法有什么区别?
(1) 两个方法都可以向线程池提交任务;
public static void main(String[] args) {ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(() -> {System.out.println("任务开始..................");int a = 10 / 0;System.out.println("任务结束..................");});executorService.submit(() -> {System.out.println("任务开始..................");int a = 10 / 0;System.out.println("任务结束..................");});executorService.shutdown();}
(2) execute
只能提交Runnable
,无返回值;
(3) submit
既可以提交Runnable
,返回值为null,也可以提交 Callable
,返回值 Future
;
(4) execute()
方法定义在Executor
接口中;
(5) submit()
方法定义在ExecutorService
接口中;
(6) execute
执行任务时遇到异常会直接抛出;
(7) submit
执行任务时遇到异常不会直接抛出,只有在调用Future
的get()
方法获取返回值时,才会抛出异常;