文章目录
一、线程
1、线程声明周期
- sleep() public static void sleep(long millis)
让正在运行的线程休眠指定的毫秒数(暂停执行)
静态方法、可由类名Thread直接调用 - join() public final void join() ——不可重写
等待调用该方法的线程执行完毕后,其他线程才能执行;
非静态方法、不可由类名Thread直接调用 - join() public final void join(long millis)
等待该线程终止的最长时间 - wait()
中断线程的执行,使线程等待;notify()和notifyAll()方法可以唤醒正在等待的线程
2、线程优先级
优先级常量
MAX_PRIORITY : 线程的最高优先级10
MIN_PRIORITY:线程的最低优先级1
NORM_PRIORITY:线程的默认优先级5(主线程默认优先级为5)
获取优先级:getPriority() 例:Thread.currentThread().getPriority()
设置优先级:setPriority(int newPriority)
3、线程同步
例:存取款操作要保证在存/取款时,当前线程不能被其他线程打断; 例如正在存款的时候不允许取款,正在取款的时候不允许存款;存款方法:saveAccount();取款方法:drawAccount();
这时,可以使用线程同步锁关键字synchronized声明存取款方法或者存取款代码块,达到该方法或代码块未执行完毕之前不允许被打断
synchronized关键字用在
- 成员方法
- 静态方法
- 代码块
例:synchronized(this) { // 静态代码块 }
synchronized关键字保证共享对象在同一时刻只能被一个线程访问
二、线程池
为什么要使用线程池?
线程实现方式 弊端:频繁创建和销毁线程
线程池实现方式 优势 :使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资
源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题
1、自动创建线程池
第一种: ExecutorService executorService1 = Executors.newCachedThreadPool();
- 这种方式创建的无界线程池,具有自动回收多余线程的功能
- 没有核心线程,只有非核心线程,执行速度较快
- 例如要执行100个任务,线程池就会分配100个非核心线程去完成这些任务
第二种 ExecutorService executorService2 = Executors.newFixedThreadPool(n);
- 其中n是核心线程数目,没有非核心线程,执行速度较慢
- 例如要执行100个任务,如果n < 100 线程池率先分配n个核心线程去干活,剩下没被执行的任务会放进阻塞队列——无界队列(linkedBlockingQueue),在n个核心线程中一旦有空闲下来的(干完活了),就会被分配去执行阻塞队列中的任务~
第三种
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
- 只有一个核心线程,可想而知当面临多个任务时,只能排着队一个一个处理
执行速度非常慢
- 阻塞队列为无界队列(linkedBlockingQueue)
第四种
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(n);
- 阻塞队列为延时队列 delayedWorkQueue
- 定时执行线程:scheduledExecutorService.schedule(new Mystask(), 5, TimeUnit.SECONDS); // 5s后执行一次
- 周期性执行线程:scheduledExecutorService.scheduleAtFixedRate(new Mystask(), 1, 3, TimeUnit.SECONDS); // 1s后开始执行,每隔3s执行一次
阿里巴巴Java开发手册有言:
4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
一句话:使用Executors创建的线程可能会因为堆积了大量的请求或者创建了大量的线程而导致OOM,所以合理的做法是我们应该根据实际的业务场景,手动创建自定义线程池,设置线程池参数
2、手动创建线程池
2.1 线程池核心参数
论如何优雅的自定义线程池:https://www.cnblogs.com/wang-meng/p/10163855.html
corePoolSize(核心线程数)
(1)核心线程会一直存在,即使没有任务执行;
(2)当线程数小于核心线程数的时候,即使有空闲线程,也会一直创建线程直到达到核心线程数;
(3)设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
maxPoolSize(最大线程数)
(1)线程池里允许存在的最大线程数量;
(2)当任务队列已满,且线程数量大于等于核心线程数时,会创建新的线程执行任务;
(3)线程池里允许存在的最大线程数量。当任务队列已满,且线程数量大于等于核心线程数时,会创建新的线程执行任务。
queueCapacity(任务队列容量,也叫阻塞队列)
也叫阻塞队列,当核心线程都在运行,此时再有任务进来,会进入任务队列,排队等待线程执行。
keepAliveTime(线程空闲时间)
(1)线程池中的线程小于核心线程数时,如果非核心线程空闲时间达到keepAliveTime时,线程会退出(关闭),直到线程池中的线程数等于或大于核心线程数;
(2)如果设置了allowCoreThreadTimeout=true,则线程会退出直到线程数等于零。
allowCoreThreadTimeout(允许核心线程超时)
总结:当线程池中的线程数多于核心线程数时,多余的线程空闲时间超过KeepAliveTime时它们就会被终止
ThreadFactory 线程工厂
- 默认使用Executors.defalutThreadFactory()创建线程
- 创建出来的线程都在同一个线程组
rejectedExecutionHandler(任务拒绝处理器)
(1)当线程数量达到最大线程数,且任务队列已满时,会拒绝任务;
(2)调用线程池shutdown()方法后,会等待执行完线程池的任务之后,再shutdown()。如果在调用了shutdown()方法和线程池真正shutdown()之间提交任务,会拒绝新任务。
2.2 线程池添加线程规则
2.3 如何设置线程池参数
线程池里的线程数量设为多少合适?
- CPU密集型(例如执行很多计算任务:计算Hash、计算加密等):最佳线程数为CPU核心数的1~2倍
- 耗时I/O型(读写数据库、文件、网络读写等等):最佳线程数一般要大于CPU核心数很多倍;参考Brain Gotze算法:
最佳线程数 = CPU核心数 * (1 + 平均等待时间 / 平均工作时间)
2.4 关闭线程池
- shutdown() 拒绝新任务,等待线程池中的线程执行完成,然后关闭;
- isShutdown() 判断shutdown方法是否已经执行,返回true时并不是说线程真的关闭了,只是表示不接收新任务了而已
- isTeminated() 判断线程池是否已经完全关闭:线程池中正在执行的任务、阻塞队列中的任务是都都已完全清空(执行完成)
- awaitTeminated(long timeout, TimeUnit unit)——要在shutdown方法后使用才有意义
timeout指的是等待 关闭线程池执行器的时间,在等待的这段时间内,如果线程都执行关闭时返回true,否则返回false; (检测作用) - shutdownNow() 立刻关闭当前线程池,立刻中断当前正在执行的线程,并返回阻塞队列中还未执行的线程队列 List< Runnable >
2.5 拒绝策略
拒绝时机:1. Executor关闭 2. 最大线程数、有界阻塞队列已饱和
四种拒绝策略
- abortPolicy 直接抛异常
- discardPolicy 直接丢弃新任务
- discardOldestPolicy 新任务到来时,将队列中”最老“的线程丢掉,给新任务腾出空间;用户对这个丢弃动作是无感的
- CallerRunsPolicy 让提交任务的线程去执行(谁给我的让谁去执行),一般是交给主线程去执行