进程(process)
进程为正在运行中的程序。
每个进程拥有自己的系统资源,内存空间和地址空间。
每个进程内部数据和状态完全独立。
线程(thread)
线程为一个程序中的单个执行流。
线程是进程的基本执行单位,每个进程由一个或多个线程组成。
每个线程可以负责不同的任务而互不干扰。
Java语言中,一个进程中,堆内存和方法区内存在线程中共享,栈内存不共享,即一个线程一个栈。
线程的生命周期
线程的生命周期可分为:新建状态(new)、就绪状态(runnable)、运行状态(running)、
阻塞状态(blocked)、死亡状态(dead)。
新建状态(new)
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时线程情况如下:
此时JVM为其分配内存,并初始化其成员变量的值。
此时线程对象没有表现出任何线程的动态特性,程序也不会执行线程的线程执行体。
就绪状态(runnable)
又叫可运行状态,当线程对象调用了start()方法之后,该线程处于就绪状态。此时线程情况如下:
此时JVM会为其创建方法调用栈和程序计数器。
该状态的线程一直处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为CPU的调度不一定是按照FIFO的顺序来调度的),线程并没有开始运行;
此时线程等待系统为其分配CPU时间片,并不是说执行了start()方法就立即执行
运行状态(running)
当CPU开始调度处于就绪状态的线程时,此时线程获得了CPU时间片才得以真正开始执行run()方法的线程执行体,该线程处于运行状态。
在一个多处理器的机器上,将会有多个线程并行执行,处于运行状态。
处于运行状态的线程不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了),线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。
对于采用抢占式策略的系统而言,系统会给每个可执行的线程分配一个时间片来处理任务;当该时间片用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。线程会从运行状态变成就绪状态,重新等待系统分配资源。
对于采用协作式策略的系统而言,只有当一个线程调用了他的yield()方法后才会放弃所占用的资源,也就时必须由该线程主动放弃所占用的资源,线程就会又从运行状态变为就绪状态。
阻塞状态(blocked)
处于运行状态的线程在某些情况下,让出CPU并暂时停止自己的运行,进入阻塞状态。
进入阻塞状态的情况:
线程调用sleep()方法,主动放弃所占用的处理器资源,暂时进入中断状态(不会释放持有的对象锁),时间到后等待系统分配cpu继续执行。
线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
线程视图获得一个同步监视器,但该同步监视器正被其他线程所持有。
程序调用了线程的suspend方法将线程挂起。
线程调用wait,等待notify/notifyAll唤醒时(会释放持有的对象锁)。
阻塞状态分类:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态。
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用)。
其他阻塞:通过调用线程的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时或者I/O处理完毕时,线程重新传入就绪状态。
在阻塞状态的线程只能进入就绪状态,无法直接进入运行状态、而就绪和运行状态之前的转换通常不受程序控制,而是由系统线程调度所决定。当处于就绪状态的线程获得处理器资源时,该线程进入运行状态。
调用yield()方法可以让运行状态的线程转入就绪状态。
死亡状态(dead)
run()或call()方法执行完成,线程正常结束;
线程抛出一个捕获的Exception或Error;
直接调用该线程stop()方法来结束该线程;
处于死亡状态的线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。
多线程的实现方式
继承Thread类
自定义一个类用于继承Thread类;
在自定义类中重写run()方法;
使用自定义类对象调用start()方法。
public class Main {
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); // 启动新线程
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("start new thread!");
}
}
实现Runnable接口
自定义类实现Runnable接口;
在自定义类中重写run()方法;
使用自定义类对象调用start()方法。
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // 启动新线程
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("start new thread!");
}
}
线程池
为什么推荐使用线程池?
创建Java线程需要给线程分配堆栈内存以及初始化内存,还需要进行系统调用,频繁的创建和销毁线程会大大降低系统运行的效率。线程池通过对线程的重用,减少了创建和销毁线程的性能开销,使程序响应更快。但同时线程池更加容易造成并发风险,诸如同步错误和死锁,还容易遭受特定于线程池的少数其他风险,诸如与线程池有关的死锁,资源不足和线程泄露。
阿里的《Java开发手册》中强制线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
JDK默认Executors提供四种线程池
newFixedThreadPool:创建一个定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建线程。
newSingleThreadExecutor:创建一个单线程化的线程池,只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序进行。
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
为什么推荐使用ThreadPoolExecutor创建线程池?
阿里的《Java开发手册》中强制使用ThreadPoolExecutor创建线程池,而不允许使用Executors 去创建。
Executors返回的线程池对象弊端如下:
FixedThread和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM(内存溢出)。
CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
ScheduledThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
ThreadPoolExecutor的构造函数
通过Excutors的源码,JDK提供的4中默认线程池都是用ThreadPoolExecutor的构造函数实现的。所以我们需要了解下ThreadPoolExecutor的构造函数。
corePoolSize:核心线程数,初始创建的线程都是核心线程数,线程池正常情况下始终保留该大小线程实例存活。
maximumPoolSize:最大线程数,当核心线程都在执行任务,任务队列满的情况下会创建非核心线程来执行任务,当非核心线程处于空闲时间,且超过keepAliveTime时,会销毁非核心线程。
keepAliveTime:存活时间,指非核心线程空闲时的存活时间。
unit:存活时间的单位,具体是TimeUnit枚举,有毫秒、秒、分钟、小时等等。
workQueue:线程池的任务队列,当线程池的核心线程都处于繁忙状态,且有新任务到来,则会进入任务队列,当任务队列满了,则会创建非核心线程执行新任务。
threadFactory:线程池创建线程实例的线程工厂,一般默认为Executors.defaultThreadFactory。
handler:线程池拒绝策略,当核心线程全部繁忙,任务队列已满,非核心线程全部繁忙,会出发线程池拒绝策略。默认为AbortPolicy,直接抛出异常并拒绝。此外还有CallerRunsPolicy,把任务交给当前线程来执行;DiscardPolicy,忽略此任务;DiscardOldestPolicy,忽略最先加入队列的任务。还可以自定义策略。
ThreadPoolTaskExecutor
ThreadPoolTaskExecutor是spring为我们提供的线程池类,他对ThreadPoolExecutor进行了封装处理。
ThreadPoolTaskExecutor配置
参数配置application.properties
# 核心线程池数
spring.task.execution.pool.core-size=5
# 最大线程池数
spring.task.execution.pool.max-size=10
# 任务队列的容量
spring.task.execution.pool.queue-capacity=5
# 非核心线程的存活时间
spring.task.execution.pool.keep-alive=60
# 线程池的前缀名称
spring.task.execution.thread-name-prefix=xxxxx
线程池配置类
@Configuration
public class ThreadPoolConfig {
@Value("${spring.task.execution.pool.core-size}")
private int corePoolSize;
@Value("${spring.task.execution.pool.max-size}")
private int maxPoolSize;
@Value("${spring.task.execution.pool.queue-capacity}")
private int queueCapacity;
@Value("${spring.task.execution.thread-name-prefix}")
private String namePrefix;
@Value("${spring.task.execution.pool.keep-alive}")
private int keepAliveSeconds;
@Bean
public Executor myAsync() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//最大线程数
executor.setMaxPoolSize(maxPoolSize);
//核心线程数
executor.setCorePoolSize(corePoolSize);
//任务队列的大小
executor.setQueueCapacity(queueCapacity);
//线程前缀名
executor.setThreadNamePrefix(namePrefix);
//线程存活时间
executor.setKeepAliveSeconds(keepAliveSeconds);
/**
* 拒绝处理策略
* CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
* AbortPolicy():直接抛出异常。
* DiscardPolicy():直接丢弃。
* DiscardOldestPolicy():丢弃队列中最老的任务。
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//线程初始化
executor.initialize();
return executor;
}
}
使用时,在方法上加上@Async注解,然后还需要在@SpringBootApplication启动类或者@Configuration注解类上添加注解@EnableAsync启动多线程注解,@Async就会对标注的方法开启异步多线程调用,注意这个方法的类一定要交给Spring容器来管理。
@Component
public class ScheduleTask {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Async("myAsync")
@Scheduled(fixedRate = 2000)
public void testScheduleTask(){
try {
Thread.sleep(6000);
System.out.println("spring1自带线程池"+Thread.currentThread().getName() + "_" + sdf.format(new Date()));
}catch (InterruptedException e){
e.printStackTrace();
}
}
@Async("myAsync")
@Scheduled(cron = "*/2 * * * * ?")
public void testAsync(){
try {
Thread.sleep(1000);
System.out.println("spring2自带线程池" + Thread.currentThread().getName() + "_" + sdf.format(new Date()));
}catch (Exception e){
e.printStackTrace();
}
}
}
输出:
怎么保证多线程按顺序执行?
在子线程中使用join()方法指定顺序
join()方法使当前线程阻塞,等待指定线程执行完后继续执行。
public class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("打开冰箱!");
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("拿出一瓶牛奶!");
}
});
final Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("关上冰箱!");
}
});
//下面三行代码顺序可随意调整,程序运行结果不受影响,因为我们在子线程中通过“join()方法”已经指定了运行顺序。
thread3.start();
thread2.start();
thread1.start();
}
}
通过倒数计时器CountDownLatch实现
CountDownLatch是一个同步工具类,是通过一个计数器来实现的,初始值为线程的数量,每当一个线程完成了自己的任务,计数器的值就相应的减1。当计数器到达0时,表示所有线程都执行完毕,然后等待的线程就可以恢复执行任务。
public class ThreadCountDownLatchDemo {
private static CountDownLatch countDownLatch1 = new CountDownLatch(1);
private static CountDownLatch countDownLatch2 = new CountDownLatch(1);
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("打开冰箱!");
countDownLatch1.countDown();
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch1.await();
System.out.println("拿出一瓶牛奶!");
countDownLatch2.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
final Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch2.await();
System.out.println("关上冰箱!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//下面三行代码顺序可随意调整,程序运行结果不受影响
thread3.start();
thread1.start();
thread2.start();
}
}
使用单一化线程池
单线程化线程池(newSingleThreadExecutor)可以串行执行所有任务。
public class ThreadPoolDemo {
static ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("打开冰箱!");
}
});
final Thread thread2 =new Thread(new Runnable() {
@Override
public void run() {
System.out.println("拿出一瓶牛奶!");
}
});
final Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("关上冰箱!");
}
});
executorService.submit(thread1);
executorService.submit(thread2);
executorService.submit(thread3);
executorService.shutdown(); //使用完毕记得关闭线程池
}
}