从0开始深入理解并发、线程与等待机制2

线程的状态/生命周期

实例化->初始状态-> (运行中(系统调度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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

—熙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值