今日内容
-
线程状态
-
线程池
-
单例设计模式
1-线程状态
当线程被创建并启动以后,它并不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那Java中的线程存在哪几种状态呢?
Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:
public class Thread {
public enum State {
/* 新建 */
NEW ,
/* 可运行状态 */
RUNNABLE ,
/* 阻塞状态 */
BLOCKED ,
/* 无限等待状态 */
WAITING ,
/* 计时等待 */
TIMED_WAITING ,
/* 终止 */
TERMINATED;
}
// 获取当前线程的状态
public State getState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
}
通过源码我们可以看到Java中的线程存在6种状态,每种线程状态的含义如下
线程状态 | 具体含义 |
---|---|
NEW(新建) | 创建线程对象 |
RUNNABLE(就绪) | start 方法被调用,但是还没有抢到CPU执行权 |
BLOCKED(阻塞) | 线程开始运行,但是没有获取到锁对象 |
WAITING(等待) | wait 方法 |
TIMED_WAITING(计时等待) | sleep 方法 |
TERMINATED(结束状态) | 代码全部运行完毕 |
线程被构建,但是还没有调用start()方法,NEW【新建】状态
线程创建后调用start()方法开始运行
当调用wait(), join(), LockSupport.lock()方法会进入到WAITING【等待】状态,而wait(long timeout), sleep(long), join(long), LockSupport.parkNanos(), LockSupport.parkUtil()增加了超时等待的功能,也就是调用这些方法后,线程会进入TIMED_WAITING【计时等待】状态
当到达了超时等待时间后,线程会切换到Runable【就绪】状态;另外线程在WAITING和TIMED_WAITING状态时也可以通过Object.notify(), Object.notifyAll()方法使线程转换到Runable状态
当线程出现资源竞争时,也就是等待获取锁的时候,线程会进入BLOCKED【阻塞】状态,当线程获取到锁时,进入到Runable状态
线程运行结束后,进入TERMINATED【终止】状态
总结:状态转换可以说是线程的生命周期,一共有六个状态
另需注意:
当线程进入到synchronized方法或者synchronized代码块时,线程切换到的是BLOCKED【阻塞】状态,而使用java.util.concurrent.locks下lock进行加锁的时候线程切换的是WAITING【等待】或者TIMED_WAITING【计时等待】状态,因为lock会调用LockSupport的方法。
2-线程池
介绍
提到池,大家应该能想到的就是水池,池子中可以养东西
而线程池,指的也是一种池子,只不过里面养的都是线程。
问题:
为什么我们需要将线程,交给线程池进行管理呢?
线程池存在的意义:
系统创建一个线程的成本是比较高的
因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程
对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。
针对这一种情况,为了提高性能,我们就可以采用线程池。
线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。
等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。
等待下一次任务的执行。
总结:
将线程对象交给线程池维护,可以降低系统成本,从而提升程序的性能
线程池使用
JDK 对线程池也进行了相关的实现,我们可以使用Executors中所提供的静态方法来创建线程池
方法 | 介绍 |
---|---|
static ExecutorService newCachedThreadPool ( ) | 创建一个默认的线程池 |
static newFixedThreadPool ( int nThreads ) | 创建一个指定最多线程数量的线程池 |
-
代码实现(默认线程池)
package com.itheima.mythreadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
// 1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
ExecutorService executorService = Executors.newCachedThreadPool();
// Executors --- 可以帮助我们创建线程池对象
// ExecutorService --- 可以帮助我们控制线程池
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
// Thread.sleep(2000);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.shutdown();
}
}
-
代码实现(指定最多线程数量)
package com.itheima.mythreadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class MyThreadPoolDemo2 {
public static void main(String[] args) {
// 参数不是初始值而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
}
}
注意:
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程
这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;
另一方面线程的细节管理交给线程池处理,优化了资源的开销。
而线程池不允许使用Executors去创建,而要通过 ThreadPoolExecutor 方式
ThreadPoolExecutor
构造方法
构造方法 | 描述 |
---|---|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) | 用给定的初始参数创建一个新的 ThreadPoolExecutor |
参数详解
参数一:核心线程数量
参数二:最大线程数
参数三:空闲线程最大存活时间
参数四:时间单位
参数五:任务队列
参数六:创建线程工厂
参数七:任务的拒绝策略
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 存活时间的单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
线程池-非默认任务拒绝策略
RejectedExecutionHandler 是 jdk 提供的一个任务拒绝策略接口,它下面存在4个子类。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
注:明确线程池最多可执行的任务数 = 队列容量 + 最大线程数
案例演示1:演示ThreadPoolExecutor.AbortPolicy任务处理策略
public class ThreadPoolExecutorDemo01 {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1 ,
3 ,
20 ,
TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) ,
Executors.defaultThreadFactory() ,
new ThreadPoolExecutor.AbortPolicy()) ;
// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
}
}
控制台输出结果
pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务
pool-1-thread-3---->> 执行了任务
Exception in thread "main" java.util.concurrent.RejectedExecutionException
控制台报错,仅仅执行了4个任务,有一个任务被丢弃了
案例演示2:演示ThreadPoolExecutor.DiscardPolicy任务处理策略
public class ThreadPoolExecutorDemo02 {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;
// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
}
}
控制台输出结果
pool-1-thread-1---->> 执行了任务
pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务
控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了
案例演示3:演示ThreadPoolExecutor.DiscardOldestPolicy任务处理策略
public class ThreadPoolExecutorDemo02 {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor;
threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());
// 提交5个任务
for(int x = 0 ; x < 100 ; x++) {
// 定义一个变量,来指定指定当前执行的任务;这个变量需要被final修饰
final int y = x ;
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);
});
}
}
}
控制台输出结果
pool-1-thread-2---->> 执行了任务3
pool-1-thread-1---->> 执行了任务1
pool-1-thread-3---->> 执行了任务4
pool-1-thread-1---->> 执行了任务100
案例演示4:演示ThreadPoolExecutor.CallerRunsPolicy任务处理策略
public class ThreadPoolExecutorDemo04 {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor;
threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());
// 提交5个任务
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
}
}
控制台输出结果
pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务
pool-1-thread-1---->> 执行了任务
main---->> 执行了任务
通过控制台的输出,我们可以看到次策略没有通过线程池中的线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行。
面试题
1. 临时线程什么时候创建
新任务提交时发现核心线程都在忙 ( 任务队列也满了 )
此时创建新的线程
public class Demo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
3,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 只提交4个线程任务, 队列没装满
for(int i = 1; i <= 5; i++){
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程任务");
}
});
}
}
}
pool-1-thread-1线程任务
pool-1-thread-2线程任务
pool-1-thread-1线程任务
pool-1-thread-2线程任务
2. 什么时候会开启拒绝策略
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝
细节补充
提交线程任务的方式 :
方法名 |
---|
void execute(Runnable command) |
Future<T> submit(Callable<T> task) |
推荐使用 submit , 因为能够同时接收 Runnable 和 Callable
3-单例设计模式
-
单例设计模式介绍
保证类的对象在内存中,只有一个对象
-
举例
-
Windows任务管理器
-
回收站
-
网站的计数器对象
-
实现步骤
饿汉式
写法一:
创建一个类,私有构造方法(为了不让其他人创建本类对象)
手动创建本类对象,用public static final修饰
外界调用这个常量
package com.lyl.single;
public class SingleDemo {
public static void main(String[] args) {
Student stu1 = Student.getInstance();
Student stu2 = Student.getInstance();
System.out.println(stu1 == stu2);
}
}
class Student{
private Student(){
}
private static final Student STU = new Student();
public static Student getInstance(){
return STU;
}
}
运行结果:
true
写法二:
-
私有化构造方法
-
定义一个 private static final 修饰的常量,常量值就是对象
-
再定义一个getInstance方法
-
在外界直接调用方法即可
public class Single {
//1.私有化构造方法 --- 不让别人创建对象
private Single(){}
//把这个变量定义为私有
private static final Single s = new Single();
public static Single getInstance() {
return s;
}
}
懒汉式
写法一:
-
弊端 : 多线程操作的时候, 很有可能会出创建多个对象
public class Single {
private Single(){}
// 懒汉式
// 核心:延迟加载
private static Single s;
public static Single getInstance() {
if(s == null){
s = new Single();
}
return s ;
}
}
写法二:
-
弊端 : 效率低
public class Single {
private Single(){}
private static Single s ;
public static Single getInstance() {
synchronized (Single.class) {
if(s == null){
s = new Single();
}
}
}
return s ;
}
}
写法三:
public class Single {
private Single(){}
private static Single s ;
public static Single getInstance() {
// 如果现在对象已经有了,而且,锁关闭了
// 为了提高代码的效率,所以,我们要在外面,再加一个非空判断
if(s == null){ // 作用:提高效率
synchronized (Single.class) {
if(s == null){ // 作用:保证唯一
s = new Single();
}
}
}
return s ;
}
}