1.1 什么是线程?线程和进程的区别?(了解)
进程:
-
进程是操作系统资源分配的基本单位。
它是一个正在执行中的程序实例,包含了程序运行所需的资源,如
内存地址空间
、文件描述符等。
-
例如,打开一个文本编辑器软件,就启动了一个进程,这个进程拥有自己独立的内存空间和系统资源来执行文本编辑任务。
线程:
-
线程是程序执行的最小单位,一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件描述符等,但每个线程有自己独立的程序计数器、栈等。
-
比如在一个多线程的网络下载程序中,不同的线程可以同时从不同的服务器下载文件的不同部分,它们共享程序的内存空间和网络连接等资源
1.2 创建线程有几种方式(必会)
1.继承 Thread 类并重写 run 方法创建线程,缺点:实现简单但不可以继承其他类。
2.实现 Runnable 接口并重写 run 方法。避免了单继承局限性,编程更加灵活,实现解耦。
3.实现 Callable 接口并重写 call 方法,创建线程。可以获取线程执行结果的返回值,并且可以抛出异常。
两者区别: 1. Callable可以获取结果值,通常要结合FutureTask一起使用。(futureTask.get()没有得到子线程结果之前,一直会阻塞调用者线程)
2.Callable 可以获取子线程的异常
4.使用线程池创建(使用 java.util.concurrent.Executor 接口)
1.3Runnable 和 Callable 的区别?
主要区别
Runnable 接口 run 方法无返回值,异常且无法捕获处理;
Callable 接口 call 方法有返回值,支持泛型, 可以获取异常信息
//Runnable异常demo
try {
Thread myThread = new Thread(() -> {
System.out.println("子线程名称:" + Thread.currentThread().getName());
throw new RuntimeException("我不想干活了");
});
myThread.start();
} catch (Exception ex) {
//这里捕捉不到异常的。
System.out.println("Runnable 无法捕捉异常,所以这句话根本不会打印出来");
}
//Callable异常demo
try {
FutureTask<String> futureTask = new FutureTask<String>(() -> {
System.out.println("子线程名称:" + Thread.currentThread().getName());
if (1 == 1) {
throw new RuntimeException("我也不想干活了");
}
return "我执行完了";
});
Thread thread = new Thread(futureTask);
thread.start();
String ret = futureTask.get();
System.out.println(ret);
} catch (Exception ex) {
//这里是能捕捉到异常的。
System.out.println("Callable 能捕捉异常,所以这句话能打印出来");
}
1.4 如何启动一个新线程、调用 start 和 run 方法的区别?
-
线程对象调用 run 方法不开启线程。仅是调用方法。
-
线程对象调用 start 使用 native开启新的子线程,使用子线程调用 run 方法
调用 start 方法可以启动新线程,并且使得线程进入就绪(Runable)状态。
而 run 方法只是 thread 的一个普通方法,还是在主线程中执行。
1.5 线程有哪几种状态以及各种状态 之间的转换?
一个线程在某一个时间点处于一个状态,这些状态不反应操作系统的线程状态。
线程状态 :
NEW 新创建的线程,尚未启动的线程状态,新创建了一个线程对象,但还没有调用start()方法。
RUNNABLE 在Jvm虚拟机中执行的线程所处的状态
BLOCKED 被阻塞等待监视器锁定的线程处于此的状态
WAITING 正在等待另外一个线程执行特定动作的线程所处的状态
TIME_WAITING 同上,它有超时时间
TERMINATED 已退出的线程处于此状态,终止
1.6 线程相关的基本方法?(必会)
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
1.线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。
wait()方法会导致当前线程进入 WATING 状态.
2.线程睡眠(sleep)
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁,
sleep(long)会导致线程进入 TIMED-WATING 状态,
3.线程让步(yield)
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。
4.线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。线程在合适的时候中断.
5.Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,会等到另一个线程结束,当前线程再由阻塞状态变为就绪状态Runable,等待 cpu 的宠幸.
6.线程唤醒(notify)
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个 线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
1.7 wait()和 sleep()的区别?(必会)
1. 来自不同的类
wait():来自 Object 类;
sleep():来自 Thread 类;
2.关于锁的释放:
wait():在等待的过程中会释放锁;
sleep():在等待的过程中不会释放锁
3.使用的范围:
wait():必须在同步代码块中使用;
sleep():可以在任何地方使用;
二. 线程池
2.1 为什么需要线程池(了解)
在实际使用中,线程是很占用系统资源的,如果对线程管理不完善的话很容易导致 系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主
要有如下好处:
1、使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建销毁时造成的消耗
2、由于没有线程创建和销毁时的消耗,可以提高系统响应速度
3、通过线程池可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等
2.2 线程池的分类(高薪常问)
1. newCachedThreadPool:创建一个可进行缓存重复利用的线程池
2. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无 界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
3. newSingleThreadExecutor : 创 建 一 个 使 用 单 个 worker 线 程 的 Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程 将会排在队列中以此执行
4. newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可 安排在给定延迟后运行命令或者定期执行
5. newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行 命令或者定期的执行
6. newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了 同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的 CPU 个数
2.3 核心参数(高薪常问)
corePoolSize:核心线程池的大小
maximumPoolSize:线程池能创建线程的最大个数
keepAliveTime:空闲线程存活时间
unit:时间单位,为 keepAliveTime 指定时间单位
workQueue:阻塞队列,用于保存任务的阻塞队列
threadFactory:创建线程的工程类
handler:饱和策略(拒绝策略)
2.4 线程池的原理(高薪常问)
线程池的工作过程如下:
当一个任务提交至线程池之后,
1. 线程池首先判断核心线程池里的线程是否已经满了。如果不是,则创建一个新的工作线程来执行任务。否则进入 2.
2. 判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入 3.
3. 判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。
如果线程池满了,则交给饱和策略来处理任务。
2.5 拒绝策略(高薪)
ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出RejectedExecutionException 异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选择重试或者放弃提交等策略
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险
ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务回退到调用者,让调用者去执行它。
2.6 线程池的关闭(了解)
关闭线程池,可以通过 shutdown 和 shutdownNow 两个方法
原理:遍历线程池中的所有线程,然后依次中断
1、shutdownNow 首先将线程池的状态设置为 STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
2、shutdown 只是将线程池的状态设置为 SHUTDOWN 状态,然后先中断闲置的线程,然后等正在干活的线程干完活再中断。