目录
1.什么是进程、什么是线程,他们之间是什么关系?
1.1.进程是什么?
是具有一定独立功能的程序,他是系统进行资源(内存)分配和调度的最小单位
进程是可以独自运行的一段程序
1.2.线程是什么?
线程是进程的一个实体,是CPU调度和分派的基本单位
他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源
1.3.两者之间的关系
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(主线程)
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源
- 线程在执行的过程中,需要协作同步。不同的进程的线程间需要利用消息通信的办法实现同步
- CPU是分给线程,即真正在CPU上运行的是线程
- 、线程是指进程内的一个执行单元,也就是进程内的可调度实体
2.Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?
创建一个线程有4种方式:
- 继承Thread类 继承的扩展性不强,java只支持单继承,如果一个类继承Thread类就不能继承其他类了
- 实现Runnable接口
- 实现Callable接口,重写call方法(有返回值)
- 通过线程池
如何区分线程?
在一个系统中有很多线程,每个线程都会打印日志,我想区分是哪个线程打印的怎么办?
thread.setName(“设置一个线程名称”); 这是一种规范,在创建线程完成后,都需要设置名称。
可以使用synchronized关键字修饰同步方法
3.Callable与Runnable区别:
Callable | Runnable | |
返回值 | 执行结果 | 无 |
异常 | 抛出异常 | 不可,必须在内部解决 |
方法 | call()方法 | run()方法 |
效率 | 高 | 低 |
4.守护线程是什么?用什么方法守护线程?
守护线程是运行在后台的一种特殊进程。
它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
在Java中垃圾回收线程就是特殊的守护线程
5.什么是串行、并发、并行?
串行的特点:前一个任务没搞定,下一个任务就只能等着。
并行是指程序在某一时刻处理事件的能力
并行的关键是你有同时处理多个任务的能力,最关键的点就是:是否是『同时』。
并发是指程程序在某一时间段处理事件的能力
并发的关键是你有处理多个任务的能力,不一定要同时
6.启动一个线程是用run方法还是start方法?
启动一个线程是调用start方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可由JVM调度并执行。
但是这并不意味着线程会马上执行,而启动以后执行的方法是run方法
7.sleep方法和wait方法的区别和共同点
共同点:两者都可以暂停线程的执行
不同点:
sleep方法没有释放锁,wait方法释放了锁
sleep方法用于暂停执行,wait方法用于线程之间的交互通信
sleep方法调用后,执行完毕线程会自动苏醒
wait方法被调用后,线程不会自动苏醒需要别的线程调用同一个对象上的notify方法或者notifyAll方法,或者可以使用wait(long timeout)超时后线程会自动苏醒。
8.线程状态
线程从执行状态出来后都不能直接回到执行状态,而是要回到待执行状态
1. 新建( new ):新创建了一个线程对象。
2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:
Blocked(堵塞)状态:
同步锁;调用了sleep()和join()方法进入Sleeping状态;执行wait()方法进入Waiting状态,等待其他线程notify通知唤醒);
(一). 等待阻塞:运行( running )的线程执行 o . wait ()方法,JVM 会把该线程放入等待队列( waitting queue )中。
(二). 同步阻塞:运行( running )的线程在获取对象的同步锁时(synchronized),若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
(三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
9.线程池的作用
限定线程的个数,不会导致由于线程过多导致系统运行缓慢或者崩溃
线程池不需要每次都去创建和销毁,,节约了资源
线程池不需要每次都去创建,响应速度更快
注:数据库线程池一样
10.线程池常用类
Java里面线程池的顶级接口是Executor,不过真正的线程池接口是ExecutorService,
ExecutorService的默认实现是ThreadPoolExecutor
普通类Executors里面调用的就是ThreadPoolExecutor,Executor是线程创建工厂类
10.1.Executors提供了四种线程池:
1)newCachedThreadPool 线程池根据需求创建线程,可扩容,遇强则强(银行一共10个窗口,只开放了3个窗口,其他7个窗口没有开放,如果人很多就要开放其余的窗口,高峰结束再回复到3个窗口状态)
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
特点:
1、线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
2、线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
3、当线程池中,没有可用线程,会重新创建一个线程
场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景
2)newSingleThreadExecutor 一个任务一个任务执行,一池一线程(例如窗口只能服务一个人,后面来的只能等待)
创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,
此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
特点: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此执行
场景: 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景
3)newFixedThreadPool (int) 一池N线程
创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变。
特征:
1、线程池中的线程处于一定的量,可以很好的控制线程的并发量
2、线程可以重复被使用,在显示关闭之前,都将一直存在
3、超出一定量的线程被提交时候需在队列中等待
场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景
4)newScheduledThreadPool (了解)
创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
场景: 适用于需要多个后台线程执行周期任务的场景
11.创建线程池的核心参数
都是通过new ThreadPoolExecutor来构造线程池,线程池相关参数的概念:
1)corePoolSize:3 线程池的核心线程数(常驻线程数)
线程池的核心线程数(常驻线程数),一般情况下不管有没有任务都会一直在线程池中一直存活
2)maximumPoolSize: 7 线程池所能容纳的最大线程数
线程池所能容纳的最大线程数,当活动的线程数达到这个值后,后续的新任务将会被阻塞。
3)keepAliveTime:4 线程闲置时的超时时长
控制线程闲置时的超时时长,超过则终止该线程。一般情况下用于非核心线程
4)unit: 时间单位
用于指定 keepAliveTime 参数的时间单位,TimeUnit 是个 enum 枚举类型,常用的有:TimeUnit.HOURS(小时)、TimeUnit.MINUTES(分钟)、TimeUnit.SECONDS(秒) 和 TimeUnit.MILLISECONDS(毫秒)等。
5)workQueue:任务队列(阻塞队列)
当核心线程数达到最大时,新任务会放在队列中排队等待执行。
6)threadFactory:线程工厂
线程工厂,它是一个接口,用来为线程池创建新线程的。
7)RejectedExecutionHandler handler: 拒绝策略(银行有10个窗口,核心是3个窗口,所有窗口都开放,等待的座位也坐满了,银行再来新的顾客,银行没有能力接受新的顾客,银行就要做一个拒绝策略,建议去别的银行)
12. 线程池工作流程
1、在创建了线程池后,线程池中的线程数为零
2、当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。当提交的任务数大于(workQueue.size() +maximumPoolSize ),就会触发线程池的拒绝策略。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
4.2 所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
13.自定义线程池
在实际项目开发中,推荐手动创建线程池
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式
这样的处理方式让写的人能够更加明确线程池的运行规则,避免资源耗尽的风险
//自定义线程池创建
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
final int index = i;
//执行
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 为第"+index+"个客户办理业务");
}
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool.shutdown();
}
}
}
14.synchronized和lock锁的区别
- synchronized内置的Java关键字 ,Lock是一个Java类
- synchronized无法判断获取锁的状态,Lock可以判断是否获得了锁
- synchronized会自动释放锁,Lock必须要手动释放锁,如果不释放锁会产生死锁
- synchronized线程1获得锁,线程2等待,Lock锁就不一定会一直等待下去,Lock.tryLock()可以尝试去获取锁,不会一直去等待,等不到就结束
- synchronized可重入锁,不可以中断的,非公平的;Lock锁可重入的,可以判断锁,非公平的
- synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码
Lock的使用
// 1、创建锁
private Lock lock = new ReentrantLock();
// 2、加锁
lock.lock();
try {
// 3、access the resource protected by the lock
// 业务代码
} finally {
// 4、解锁
lock.unlock();
}
15.Volatile
Volatile是Java虚拟机提供的轻量级同步机制
1 保证可见性
可见性就是当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值
2 不保证原子性
原子性就是不可分割的,线程执行任务的时候,是不可分割的,要不同时成功,要不同时失败
3 禁止指令重排
指令重排是指在程序执行过程中,为了提高性能, 编译器和CPU可能会对指令进行重新排序