目录
JAVA多线程
进程、线程、协程
线程上下文切换
Java中的线程调度算法
守护线程
线程的生命周期
5种基本状态
5种状态的转换
线程间通信
线程安全
什么是线程安全?
java中如何保证多线程的运行安全?
死锁
死锁的必要条件
防止死锁
创建线程的方式
继承Thread类
实现Runnable接口
实现Callable接口
线程池
线程池使用的时机(何时使用线程池?)
使用线程池的好处
线程池四个基本组成部分
线程池七大参数
使用线程池的流程
如何配置线程池中的线程数
四种常见线程池
锁
悲观锁&乐观锁
公平锁&非公平锁
独占锁&共享锁
可重入锁
自旋锁
为什么要使用多线程?
1 避免阻塞
2 避免cpu空转
3 提升性能
在单核上,CPU决定线程a和b的执行权。在双核上,并行,一个执行线程a,另一个执行线程b。协程则是由程序员控制执行权。例如可以让一个协程a同时在两个核上运行,执行完后再使用双核同时运行协程b。
*有了进程为什么还要有线程?
cpu在执行一个已经运行的线程的时候切换到另一个等待获取CPU执行权的线程。这个切换过程就是线程上下文切换。
*如何减少线程上下文切换,提高操作系统效率?
也称后台线程,例如GC线程。守护线程和前台线程一起运行,当前台线程执行完,守护线程也自动结束。
就绪状态转换为运行状态:当此线程得到处理器资源
运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。
运行状态转换为死亡状态:当此线程执行体执行完毕或发生了异常。
*sleep()和wait()区别
1.同步(使用synchronized)
本质上就是共享内存上的通信。多个线程需要访问同一个共享变量,谁拿到了锁,谁就可以执行。
2.while轮询
线程a不断改变条件,b不停地通过while语句检验这个条件(list.size()==5)是否成立,从而实现了线程间通信,这种方法会浪费cpu资源。
3.wait/notify机制
条件满足时,线程b调用notify()通知线程a,也就是唤醒线程a,并且让他进入可运行状态,提高了cpu利用率。
4.管道通信
使用java.io.PipeInputStream和java.io.OutPutStream进行通信。通过管道将一个线程中的消息发送给另外一个。
如果你的代码在多线程下执行和单线程下执行永远能获得一样的结果,那么你的代码就是线程安全的。
1.原子性:同一时间只能有一个线程对同一数据进行操作。
2.可见性:当一个线程对数据进行操作,可以及时被其他线程看到。
3.有序性:代码的执行和语句的顺序保持一致。
死锁指多个进程因竞争资源而进行相互等待的僵局。
1.互斥条件
一个资源每次只能被一个进程使用。
2.请求与保持条件
进程已经获得了一个资源,但又提出了新的资源请求,而该资源已被其他进程占有;此时请求进程被阻塞,但是又对已获得的资源保持不放。
3.不可剥夺条件
进程在获得的资源使用完毕前,不能被其他进程强行夺走,只能由获得该资源的进程自己来释放。
4.循环等待条件
指若干进程间形成的首尾相接的循环等待资源的关系。
1.加锁顺序
确保所有线程都按相同的顺序来获得锁,那么死锁就不会发生;线程只有获取了前面的锁之后,才能获取后面的锁。
2.加锁时限
在尝试获取锁时,加一个超时时间,如果超过了这个时限,则放弃对该锁的请求;
如果没有在时限内成功获得所有需要的锁,则会回退并释放所有已经获得的锁,然后等待一段时间后重试。
3.死锁检测
*start()和 run()的区别?
start()用于启动一个新的线程,而且start()内部调用了run()方法,所以它会开启新线程,并执行run()方法。当调用run()方法时,只会是在原来的线程中调用方法,没有新的线程启动。
*Thread、Runnable、Callable的区别
线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。底层由队列实现。
当创建任务时间+销毁线程时间远大于在线程中执行任务的时间,则可以采用线程池,用来提高服务器性能。
1.线程池管理器
用于创建并管理线程池,包括创建线程池、销毁线程池、添加新任务。
2.工作线程
线程池中线程,在没有任务时处于等待状态,可以循环执行任务。
3.任务接口
每个任务必须实现的接口,以供工作线程调度任务的执行;它规定了任务的入口、任务执行完后的收尾工作、任务的执行状态等。
4.任务队列
用于存放没有处理的任务,提供一种缓冲机制。
Executors为我们封装好了4种常见的线程池。
1.定长线程池(FixedThreadPool)
池内线程类型:核心线程
池内线程数量:固定
处理特点:核心线程处于空闲状态时也不会回收,除非线程被关闭;
当所有线程都处于活动状态时,新的任务都会处于等待状态,知道有线程空闲出来;
任务队列无大小限制(超出的任务会在队列中等待)。
应用场景:控制线程最大并发数
2.定时线程池(ScheduledThreadPool)
池内线程类型:核心&非核心线程
池内线程数量:核心线程数量固定;非核心线程无限制
处理特点:当非核心线程执行超时,则会被立即回收
应用场景:执行定时或周期性任务
3.可缓存线程池
池内线程类型:非核心线程
池内线程数量:不固定(可无限大)
处理特点:优先利用闲置线程处理新任务(会重用线程);
无线程可用时,会新建线程。任何线程任务到来时都会立即执行,不需要等待;
灵活回收空闲线程(具备超时机制,空闲超过60s才回收,全部回收时几乎不占系统资源)
应用场景:执行数量多、耗时少的线程任务
4.单线程化线程池(SingledThreadExecutor)
池内线程类型:核心线程
池内线程数量:1个
处理特点:保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步问题。
应用场景:单线程(不适合并发但可能引起IO阻塞以及影响UI线程响应的操作,如数据库操作等)
*四种线程池的弊端
功能线程池虽然方便,但不建议使用,建议通过ThreadPoolExecutor的方式,避免资源耗尽的风险。
定长线程池和单线程化线程池:
堆积的请求处理队列均采用LinkedBlockedQueue,可能会耗费很大内存,甚至造成OOM(内存溢出)。
定时线程池和可缓存线程池:
他们的现成最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至造成OOM。
锁是一种同步机制,用于在有许多执行线程的环境中强制对资源进行访问限制;锁可以强制实施排他互斥、并发控制策略。
悲观锁:认为每次拿数据时别人都会对数据进行修改;
乐观锁:认为每次拿数据时别人都不会对数据进行修改。
公平锁:线程按顺序依次获取锁;
非公平锁:线程不按申请顺序获得锁,谁都有可能获得锁。
独占锁:同一时间只能被一个线程持有;
共享锁:同一时间可以被多个线程占有。
外层方法获得锁的时候,内层方法自动获得该锁。
是指当一个线程在获取锁的过程中,如果所已经被其他线程获取,那么该线程将循环等待,然后判断锁是否能够被成功获取,直到获取到锁才会退出循环。
待补充