Java 从多线程到并发编程(四)——线程礼让yield 线程强制执行join 线程自我阻塞sleep 线程优先级 守护线程 守护进程

前言 ´・ᴗ・`

  • 继上一次我们学习了线程优雅的停止方式以及线程的状态有哪些

  • 本节将会帮助你了解…

    • yield —— 线程重新洗牌
    • join —— 线程蛮横插队
    • 线程优先级 —— 只是建议哟
    • 守护进程 —— 主人还在 我就一直守护着他

线程洗牌yield

yield本身的意思是 屈服; 让步; 放弃; 缴出;
说白了就是投降 因此有人称之为 线程礼让 但实际上 我认为并非如此

其效果只不过是重新洗牌而已 已经获取CPU资源的线程 自愿放弃这次权利 退出来 然后再进行一次优先级比赛 而不是真的权利直接拱手相让(那是被别的线程join了才会导致这样)

更具体来说 yield使当前线程从执行状态(运行状态)变为可执行态(就绪状态) 释放了当前线程的CPU执行权 但是保留可执行权

我们结合join来做个demo你就清楚了

线程插队join

join的作用很有意思 按理还说多线程就是要不停的切换 当有个线程阻塞了 换到别线程执行的去 这样才高效

但是有些线程具有特权 必须他执行完才能给别人执行 于是就可以用join插别的线程的队 也就是真的巧取豪夺别人的CPU权利

比如这里我们用子线程插主线程的队:

package com.base.threadlearning.threadState;


// join 插队 就是让他先运行 杂碎都给我让开:)
public class ThreadJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("vip "+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        ThreadJoin threadJoin = new ThreadJoin();
        Thread thread = new Thread(threadJoin);
        thread.start();

        for (int i = 0; i <30; i++) {
        	System.out.println("main"+i);
            if(i==5){
                thread.join(); //开始插队!!
            }
            
        }
    }
}

结果如下

vip0
main0
vip1
main1
vip2
main2
vip3
main3
vip4
vip5
vip6
vip7
vip8
vip9
main4
main5
main6
main7
main8
main9
main10
main11
main12
main13
main14
main15
main16
main17
main18
main19
main20
main21
main22
main23
main24
main25
main26
main27
main28
main29

前面还是轮询着来 i==3之后 vip开始插队 ——不允许main线程跑了 vip线程独享CPU

这里值得注意的是 join只需要执行一次 就可以让vip的优先级永远比之前被阻塞的线程高 因此使用一次 就可以一直持续到vip结束

换言之 只要vip有故障阻塞了 那CPU就会永远卡在那里 效率很低

我们看看join的定义,让你从另一个视角去理解:

如果线程A调用了线程B的join方法,线程A将被阻塞,等待线程B执行完毕以后,线程A才得以被执行

我们看看代码,是不是这样?main线程执行了我们线程的join方法,导致被阻塞 这听起来好像是,main对我们线程说,come on,来阻塞我吧:)来插我的队

yield

如果我们yield呢?你觉得有用嘛?其实相对来说,效果不那么明显, 说了只是重新洗牌 所以看起来混乱一点 如下示例:

public class ThreadJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Thread.yield();
            System.out.println("vip "+i);
        }
    }

我们将上个例子中run函数添加了yield 也就是遇到别的线程 第一次进入他会退出来要求再次比拼优先级 有一点 要把别人打服气为止的感觉(不服气咱们再来一次 再比一次?)

结果如下:

vip0
main0
main1
main2
vip1
main3
vip2
vip3
vip4
vip5
vip6
vip7
vip8
vip9
main4
main5
main6
main7
main8
main9
main10
main11
main12
main13
main14
main15
main16
main17
main18
main19
main20
main21
main22
main23
main24
main25
main26
main27
main28
main29

首先可以发现 i<3的时候 明显main和vip不再像正常情况那样几乎等概率分布 而是变得非常混乱 这说明确实洗过牌了 你多运行几次 每次结果都不一样。。。

当然了 一旦join以后 优先级将会不可避免的提升 再洗牌yield也没有意义——因为出来再比优先级 vip还是会更高!

优先级

首先注意 这里的优先级只是人为加上去的而已 并非客观上的优先级
我们前面说了在这里插入图片描述
截图这里优先级是客观上 线程调度器认为的优先级不一定与我们设定的优先级相同

换言之 你只是建议线程调度器 哪个线程的优先级有多高 至于具体 那要看线程调度器的心情

当然了 一般线程调度器会听取建议 或者你可以理解为 增大了概率 但不是一定

我们看个好玩的例子

package com.base.threadlearning.threadState;

import lombok.SneakyThrows;

public class ThreadPriority implements Runnable{

    private static void testPriority(){
        Thread temp = Thread.currentThread();
        System.out.println(temp.getName()+" --- "+temp.getPriority());
    }
    @SneakyThrows
    @Override
    public void run() {
        while(true){
            Thread.sleep(100);
            testPriority();
        }
    }

    public static void main(String[] args) {
        testPriority();

        ThreadPriority threadPriority = new ThreadPriority();

        for (int i = 1; i <= 10; i++) {
            Thread thread = new Thread(threadPriority);

            thread.setPriority(i);// 优先级设置先于start最好
            thread.start();
        }
    }
}

main --- 5
Thread-8 --- 9
Thread-2 --- 3
Thread-3 --- 4
Thread-6 --- 7
Thread-5 --- 6
Thread-7 --- 8
Thread-9 --- 10
Thread-1 --- 2
Thread-0 --- 1
Thread-4 --- 5
// 以上第一轮 受到start先后顺序影响 可以不看 

Thread-6 --- 7
Thread-3 --- 4
Thread-7 --- 8
Thread-0 --- 1
Thread-8 --- 9
Thread-9 --- 10
Thread-1 --- 2
Thread-4 --- 5
Thread-5 --- 6
Thread-2 --- 3
Thread-8 --- 9
Thread-3 --- 4
Thread-2 --- 3
Thread-9 --- 10
Thread-6 --- 7
Thread-7 --- 8
Thread-1 --- 2
Thread-0 --- 1
Thread-4 --- 5
Thread-5 --- 6
Thread-6 --- 7
Thread-2 --- 3
···

我们发现 即便排除了第一轮start的时候有先后顺序的干扰 后面几轮还是出现了性能倒置 即高优先级的反而没有被线程调度器优先执行

这说明了我们的优先级不过是建议而已

一般默认是5 大家公平竞争

yield的补充

其实有没有想过一个问题,yield礼让,或者说洗牌,那么如果我礼让的线程优先级(我说的是我们设定的优先级)本身比我低,那礼让会成功吗?答案是不会!因此礼让yield的前提是,你的礼让对象优先级至少与你平起平坐,要大于等于。

sleep

sleep我们经常用,他是Thread类的一个静态方法,可以将当前线程无条件阻塞,指定长度的时间,而且这个无条件,意味着无论优先级多高,一定阻塞,让出CPU。相对而言,yield只不过是重新洗牌,意味着下一轮时间片,他还是有机会受到CPU的青睐,

而且另外一个角度来说,sleep后边一定进入阻塞状态,但是yield后,进入什么状态?就绪(Runnable)状态

守护线程 守护进程

其本质就是为所有非守护线程提供服务的线程;换句话说,任何一个守护线程都是整个JVM中所有非守护线程的保姆,或者类似fate的master和英灵的关系,守护,意味着master没了,英灵,或者说守护者也将消失

守护进程呢?也是同理,守护一个进程,如果master进程没了,自己也没了

我们的垃圾回收进程GC就非常契合这个定义——当其他进程结束 也就不可能会有垃圾了 这时守护进程才可以下班 还有日志记录之类的也是同样的

所以 由于守护线程的终止是自身无法控制的,因此千万不要把IO、File数据库事务操作等重要操作逻辑设置为守护进程 因为不可控制

当然反过来 守护线程特别适合后台运行的进程逻辑 因为它能够“自我了断” 即便自己跑的是死循环

当然 并不是真的自我了断 其实际的原理 其实是JVM只管那些用户线程(或者说非守护线程),一旦非守护线程都没了 被回收了 JVM就下班了 任务完成了 停止运行了 因此守护线程也随之停止

package com.base.threadlearning.threadState;

/**
 * 用户线程
 * 守护线程(如GC 日志记录)
 * 虚拟机只用管用户线程 守护线程放着就好
 */
public class ThreadDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);

        thread.start();

        new Thread(you).start();

    }



}

class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("Daemon guard you");
        }
    }
}

class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("我还活着");
        }
        System.out.println("GG");
    }
}

运行结果:
在这里插入图片描述
在用户进程GG了以后 守护进程运行了一会儿也停止了 jvm停止

总结 ´◡`

下一节 Java 从多线程到并发编程(五)—— 并发 同步 锁 阻塞 synchronized概念浅析

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值