线程的状态/生命周期
实例化->初始状态-> (运行中(系统调度yield())-> 就绪 ) 运行态 ->终止
-> 等待(某个条件没有满足)
-> 超时等待(某个条件没有满足,不过等到某个时间到了,尽管条件还没有满足,依然会进入运行态)
-> 阻塞态(只有被synchronized内置锁关键字阻塞的才是阻塞态)
系统调度
yield():使当前线程让出CPU的占有权,但让出时间是不可设定的,也不会释放锁资源,同时执行yield()的线程进入就绪态后有可能再次被操作系统选中,再次执行
为什么CurrentHashMap中在初始化中调用了yield()方法?
首先new CurrentHashMap()创建一个实例的时候,并不会为这个集合分配空间,只有当调用了put方法之后,才会执行初始化操作。CurrentHashMap是为了应付在并发下多个线程同时操作HashMap而提出来的,是线程安全的。所以执行put()方法的可能有很多个线程,但是这个实例只有一个,因此对于这个实例初始化来说只需要一个线程就足够了,那么其他线程是需要等待的,初始化只是在内存中划出一块区域,这是很快的,那么如果只让一个线程执行,其他线程都等待的话,会产生上下文切换,会降低速度,增加开销。所以执行系统调度yield()方法,让执行初始化的线程先执行,其他线程把cpu让出来。
线程的调度
协同式线程调度:
什么时候让出CPU由在占用CPU的线程本身决定
好处:可以避免线程同步问题,上下文切换比较少
坏处:可能导致其他线程一直等待
抢占式线程调度:
好处:提高了CPU利用率
Java中的线程调度属于抢占式线程调度,为什么?
在Java中,Thread.yield()可以让出CPU执行时间,但是对于获取执行时间,线程本身是没有办法的。对于获取CPU执行时间,线程唯一可以使用的手段是设置线程优先级,Java设置了10个级别的程序优先级,当两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。Java中线程优先级是通过映射到操作系统的原生线程上实现的,所以线程的调度最终取决于操作系统,而操作系统中的线程优先级不一定能和Java中的一一对应,所以Java优先级并不靠谱
线程实现
内核线程实现(1:1)
一个实例对应一个操作系统线程(也就是在语言层面和操作系统层面上的1:1)
好处:可以专心写业务代码然后运行交给操作系统,方便
坏处:线程之间的同步需要系统调用,这样会产生上下文切换,每new一个线程实例,操作系统os中就需要映射一个线程
用户线程实现(1:N)
n个实例对应一个操作系统线程(需要自己实现调度),在语言层面实现一整套线程的相关机制
好处:所有线程都在语言层面处理完了,低消耗,减少上下文切换
坏处:操作系统带来的线程好处,创建、销毁、调度就没了
混合实现(N:M)
n个实例对应m个操作系统线程
Jvm级别的每一个线程都需要映射到操作系统上的原生线程上,因此线程的调度,不能干预
协程
协程其实就是一种用户线程的实现
出现的原因
由于微服务的不断发展,内核线程实现在很多场景已经优点不适宜了
最大的优点
轻量 一个协程栈 几百字节,而线程创建什么都不做就需要1M
局限性
需要自己实现调度
适用场景
大并发,很多个用户来做请求,IO密集型(和网络、磁盘打交道)在规模上做了增强,不适用于计算密集型(从内存中不断取数据去做、去算的)
java中的协程
纤程
java中的协程库
quasar
注意:需要引入纤程的jar包
-javaagent:D:\Maven\repository\co\paralleluniverse\quasar-core\0.7.9\quasar-core-0.7.9.jar
其他
JDK19正式引入作为虚拟线程,不过官方不建议在生产环境使用
守护线程
守护线程和一般应用线程有什么区别
守护线程的作用:支持主线程,占资源,且都存在于JVM中,一般实现内存清理、垃圾回收、接收信号,用来守护Java内存资源的回收与调度
主线程结束,但应用线程有可能不会结束,因为线程之间是独立的。
JVM中只有一个主线程其他都是守护线程的话,主线程结束,其他守护线程也会相继结束
如果有一个非守护线程的话,JVM就不会退出
怎么让应用线程变成守护线程?
应用线程.setDaemon(true);即可变为守护线程
例如:useThread.setDaemon(true);
管道输入输出流
场景:先将文件写入到本地磁盘,然后从文件磁盘读出来上传到云盘,但是通过Java中的管道输入输出流一步到位,则可以避免写入磁盘这一步
Java中的管道输入输出流主要包括4种具体实现
PipedOutputStream、PipedInputStream、PipedReader和PipedWriter
前两个面向字节处理二字节、后两个面向字符。下面展示一个示例:在控制台中输入字符串,控制台会输出你输入的字符串
public class Piped {
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
/* 将输出流和输入流进行连接,否则在使用时会抛出IOException*/
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive = 0;
try {
/*将键盘的输入,用输出流接受,在实际的业务中,可以将文件流导给输出流*/
while ((receive = System.in.read()) != -1){
out.write(receive);
}
} finally {
out.close();
}
}
static class Print implements Runnable {
private PipedReader in;
public Print(PipedReader in) {
this.in = in;
}
@Override
public void run() {
int receive = 0;
try {
/*输入流从输出流接收数据,并在控制台显示
*在实际的业务中,可以将输入流直接通过网络通信写出 */
while ((receive = in.read()) != -1){
System.out.print((char) receive);
}
} catch (IOException ex) {
}
}
}
}
线程中的协调:join()方法
join()
把指定的线程加入到当前线程,可以将两个交替执行的线程合变为顺序执行。比如在线程B中调用了线程A的join() 方法,直到线程A执行完毕后,才会继续执行线程B剩下的代码
面试题:
现有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
在T3中采用T2.join(),在T2中调用T1.join()