1.什么是线程?线程和进程的区别?
线程:是进程的一个实体,是cpu调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。
特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元是相互独立的,线程之间内存共享,这使得多线程编程可以拥有更好的性能和用户体验。
2.创建线程的几种方式?
1.继承Thread类并重写run方法创建线程,实现简单但不可以继承其他类
2.实现Runnable接口并重写run方法。避免了单继承的局限性,编程灵活,实现解耦。
3.实现Callable接口并重写了call方法,创建线程。可以获取线程执行结果的返回值,并且可以抛出异常。
4.使用线程池创建
package cn.tedu.day01;
import java.util.Random;
import java.util.concurrent.Callable;
/*创建一个类,实现Callable接口(泛型类型自定义)
重写Callable接口中的call方法
创建Callable接口的实现类对象
创建FutureTask创建,把Callable实现类对象传入到FutureTask构造方法中
创建Thread对象,把FutureTask对象传入到Thread类的构造方法中
开启线程
通过FutureTask对象调用get方法,拿到返回值,线程才结束。
* */
public class ThreadDemo3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);
}
}
package cn.tedu.day01;
import java.util.concurrent.FutureTask;
public class ThreadTest3 {
public static void main(String[] args) throws Exception{
ThreadDemo3 threadDemo3 = new ThreadDemo3();
FutureTask<Integer> futureTask = new FutureTask<>(threadDemo3);
Thread thread = new Thread(futureTask);
thread.start();
//拿到线程返回值时,线程才结束
Integer integer = futureTask.get();
System.out.println(integer);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
3.Runnable和Callable的区别?
Runnable接口run方法无返回值;Callable接口call方法有返回值,支持泛型
Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息
4.如何启动一个新线程,调用start和run方法的区别?
线程对象调用run方法不开启线程,仅是对象调用方法。
线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。
5.线程有哪几种状态以及各种状态之间的转换?
1.新建状态。生产线程对象,但是并没有调用该对象的start方法
2.就绪状态。当调用了线程对象的start方法之后,此时线程调度程序还没有把该线程设置成当前线程,处于就绪状态
3.运行状态。线程调度程序将处于就绪状态的线程设置成当前线程,此时线程进入了运行状态,可以执行run函数中的代码
4.阻塞状态。阻塞状态是线程因为某种原因放弃了cpu使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞情况分三种:
(1)等待。通过调用线程的wait()方法,让线程等待某工作的完成
(2)超时等待。通过调用线程的sleep()或join()或者发出了I/O请求时,线程会进入到阻塞状态。当sleep状态超时,join()等待线程终止或者超时、或者I/O处理完毕了,线程重新进入就绪状态。
(3)同步阻塞-线程在获取synchronized同步锁失败(因为锁被其它线程占用),它会进入同步阻塞状态。
5.死亡状态。线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
6.线程相关的基本方法?
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
7.wait()与sleep()的区别?
- 来自不同的类
- 关于锁的释放
- 使用的范围
- 是否需要捕获异常
线程池
1.为什么需要线程池?
在实际使用中,线程是很占用系统资源的,如果对线程管理不完善的话就很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程。
1.线程池可以重复利用已有的线程继续执行任务,避免线程在创建消耗时造成的消耗
2.由于没有线程创建和消耗时的消耗,可以提高系统响应速度
3.通过线程池可以对线程进行合理的管理,根据系统的承受能力可以随时调整可运行线程数量的大小。
2.线程池的分类
1.newCachedThreadPool:创建一个可进行缓存重复利用的线程池
2.newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
3.newSingleThreadExecutor:线程池最多执行一个线程,之后提交的线程将会排在队列中以此执行。
4.newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可 安排在给定延迟后运行命令或者定期执行
5.newScheduledThreadPool:创建一个线程池,他可安排在给定延迟后运行命令或者定期执行。
6.newWordStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多个线程在执行,如果不传并行级别参数,将默认为当前系统的CPU个数。
3.核心参数:
corePoolSize 核心线程池的大小
maximumPoolSize 线程池能创建线程的最大个数
keepAliveTime 空闲线程存活时间
unit:时间单位,为keepAliveTime指定时间单位
workQueue:阻塞队列,用于保存任务的阻塞队列
ThreadFactory:创建线程的工程类
handler:饱和策略(拒绝策略)
4.线程池的原理
- 一个任务,已存在的一个核心线程,可以解决
- 多个任务,等待的任务!!没有超过阻塞队列长度,存放在阻塞队列中,等待核心线程依次执行!
- 多个任务,等待的任务超过了阻塞队列的最大长度,就会开始创建新的线程,如果新建线程数+核心线程数>最大线程数,直接报错;如果没有超过,那就正常创建,核心线程增加,分摊任务!!
5.拒绝策略(保护策略)
ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出 RejectedExecutionException 异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选 择重试或者放弃提交等策略
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常,相对而 言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常,相对而 言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失风险
- ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务 回退到调用者,让调用者去执行它。
6.线程池的关闭。
原理都是:遍历线程池中的所有线程,然后依次中断。
shutdownNow 首先将线程池的状态设置为 STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表
shutdown 只是将线程池的状态设置为 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程