Java Web 实战 03 - 多线程基础(2)

news2024/10/6 10:35:26

Java Web 实战 03 - 多线程基础篇 2

  • 二 . Thread类常见方法
    • 2.1 Thread 的常见构造方法
    • 2.2 Thread 的几个常见属性
      • getId()
      • getName()
      • getState()
      • getPriority()
      • isDaemon()
      • 案例 : 实现 getId()、getName()、 getState()、getPriority()、isDaemon()、isAlive()
    • 2.3 启动一个线程-start()
    • 2.4 中断线程(线程执行结束)
      • 2.4.1 手动创建的标志位
      • 2.4.2 Thread自带的标志位
    • 2.5 线程等待 join
      • 2.5.1 join 是怎么回事?
      • 2.5.2 栗子1 : 让 main 等待 t2 , t2 去等待 t1
      • 2.5.3 栗子2 : 控制 main 先运行 t1 , t1 执行完再运行 t2
      • 2.5.4 带参数版本的 join
    • 2.6 获取到线程引用 : Thread.currentThread()
    • 2.7 线程的休眠 : sleep
  • 三 . 线程的状态
    • 3.1 通过代码来看线程的状态
    • 3.2 线程状态和状态转移的意义

大家好 , 这篇文章给大家带来的是多线程相关的基础知识 , 我们会介绍一下Thread 类常见的方法都有什么 , 以及启动线程、中断线程、线程等待、获取线程引用、现成的休眠等问题 , 然后再给大家介绍一下线程的状态都有什么这几个问题。
上一篇文章的链接在这里 , 大家移步观看
http://t.csdn.cn/JVErX
由于 C 站的编辑器不太好用 , 导致许多排版没能生效 , 大家可移步至这里观看

https://www.yuque.com/jialebihaitao/study/qzym2pw332lm6k7q?singleDoc# 《2. 多线程 (基础)》

感谢大家的支持~

二 . Thread类常见方法

2.1 Thread 的常见构造方法

方法说明
Thread()创建线程对象 (已经用过了)
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名

其中第一个和第二个我们已经使用过

  1. Thread() : 自己创建线程的子类 , 再去 new 他的实例
Thread t = new MyThread();
  1. Thread(Runnable target) : 使用 Runnable 创建一个任务 , 然后将 Runnable 的任务放到线程中 , 这样也可以创建一个线程
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
  1. Thread(String name) : 这个操作是给线程起名字,我们之前看到的 Thread-0 就是 Java 帮我们命名的,线程在操作系统里面是没有名字的,Java 为了帮助程序员便于理解,就在 JVM 里面给对应的 Thread 对象起了个名,这个线程是和内核中的线程一一对应的 , 这个名字对于程序的执行 , 没有任何影响 , 对于程序员来说调试挺有用的
Thread t3 = new Thread("这是我的名字");
  1. Thread(Runnable target, String name) : 既实现了命名 , 又实现了使用 Runnable 对象创建线程对象
public class Demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("Hello MyThread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"俺的线程");
        t.start();

        while(true) {
            System.out.println("Hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

我们使用 jconsole 工具查看当前线程 , 就发现有我们自己重新命名的线程了
image.png

2.2 Thread 的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isinterrupted()

getId()

获取到的是 线程 在 JVM 里面的身份标识

线程里面的身份标识是有好几个的

  1. 内核的 PCB 上有标识
  2. 用户态线程库里面也有标识 (pthread , 操作系统提供的线程库)
  3. JVM里面也有标识 (JVM 的 Thread 类底层也是调用操作系统的 pThread 库)

这三个标识各不相同,但是目的都是作为身份的区分,我们可以理解为身份标识的各种小名

getName()

获取在Thread的构造方法里面,传入的姓名 (刚才在 Thread 构造方法中传入的名字)

getState()

获取到 PCB 里面的状态 , 这个状态表示线程当前所处的一个情况 . 此处得到的状态是 JVM 里面设立的状态体系 , 这个状态比操作系统内置的状态要更丰富

getPriority()

获取到线程的优先级 , 优先级高的线程理论上来说更容易被调度到

isDaemon()

判断是不是后台线程
Daemon的意思是 “守护线程” 的意思 , 我们可以理解为 “后台线程”
线程分为"前台线程"与"后台线程" , 我们可以通过类比手机的正在运行应用 (前台应用) 与后台应用来理解前台线程与后台线程
一个线程 , 创建出来默认就是前台线程 , 前台线程会阻止进程结束 , 进程会保证所有的前台线程都执行完毕 , 才会退出 . main 线程就是一个前台线程
如果我们屏蔽了 main 线程 , 正常情况下线程就会退出
但是运行并非如此
image.png
这是因为我们上面新建的线程默认就是前台线程 , 我们需要将自己创建的线程修改成后台线程

public class Demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("Hello MyThread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"俺的线程");
        t.setDaemon(true);// 设置成后台线程
        t.start();

        // while(true) {
        //     System.out.println("Hello main");
        //     try {
        //         Thread.sleep(1000);
        //     } catch (InterruptedException e) {
        //         e.printStackTrace();
        //     }
        // }
    }
}

这样我们的线程就会嗖的一下执行完
image.png
有个小问题 :
❗ 我们发现 , 怎么运行了多次 , 怎么有的时候打印有的时候就不打印呢?
image.png
✅ 举个栗子 : 当我们刚打完一局游戏 , 想要打开 B 站去学习 , 这时候点开了 B 站 , B 站这个时候就是"前台应用" , 必须是在执行的 , 不能被销毁

前台线程 : 前台线程会阻止进程结束 , 一个线程创建出来默认就是前台线程 , 进程会保证所有前台进程执行完毕才结束程序

后台的游戏就被隐藏到任务栏里面了 , 其实还运不运行已经无所谓了 , 因为我们已经玩完了

后台线程 : 后台线程不会阻止进程结束 , 比如 : main线程执行结束 , 整个线程就结束了

这是因为我们把 t 线程设置成了后台线程 , 当 main线程执行完毕整个程序就执行完了 , 我们这里并未对 main 线程做任何操作 , 所以有可能是在 main 线程结束之前运行了一下 t 线程 , 也有可能是直接就执行结束了 , t 线程还没来得及执行


案例 : 实现 getId()、getName()、 getState()、getPriority()、isDaemon()、isAlive()

接下来 , 我们写一个方法 , 来实现 getId()、getName()、 getState()、getPriority()、isDaemon()、isAlive()

public class Demo8 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("Hello MyThread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"俺的线程");

        System.out.println(t.getId());// 获取线程ID
        System.out.println(t.getName());// 获取线程名称
        System.out.println(t.getState());// 获取线程状态
        System.out.println(t.getPriority());// 获取线程优先级
        System.out.println(t.isDaemon());// 获取是否是后台线程
        System.out.println(t.isAlive());// 获取线程是否存活
    }
}

image.png

2.3 启动一个线程-start()

我们之前已经使用过这个方法了
给大家强调一点 :
(安排任务)创建 Thread 实例 , 并没有真正的在操作系统内核中创建出线程 . 这里的任务是通过 Thread 的 run 或者 Runnable 或者 lambda 来体现具体的任务内容

run 方法只是描述任务 , 这一点要与 start 方法区分开

(发令枪响)调用 start 方法 , 才是真正在系统里创建出线程 , 才真正开始执行任务

2.4 中断线程(线程执行结束)

线程的执行结束 , 其实就是让线程的入口方法执行完成 , 线程也就执行结束了

普通线程的入口方法就是 run 方法
主线程的入口方法就是 main 方法

那么我们想要中断线程 , 其实就只需要把线程的入口方法执行结束即可
我们有两种方法来中断线程

2.4.1 手动创建的标志位

我们可以自己设置个标志位来区分线程是否要结束

public class Demo11 {
    // 用一个布尔变量表示线程是否要结束
    // 注意使用成员变量
    private static boolean flag = false;
    
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!flag) {
               System.out.println("线程运行中...");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           System.out.println("新线程执行结束...");
        });

        t.start();

        try {
            Thread.sleep(5000);//休息5s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("这里可以控制新线程退出");
        flag = true;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T23oPRJO-1678059222242)(https://jialebihaitao.oss-cn-beijing.aliyuncs.com/image-20220816151113932.png#id=QAeX9&originHeight=1030&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AxkFtmoh-1678059222242)(https://jialebihaitao.oss-cn-beijing.aliyuncs.com/image-20220816152245167.png#id=Levjt&originHeight=757&originWidth=1815&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

这种方案是可行的 , 其实 Thread 自己就提供了这种方法 , 内置了标志位 , 就不需要我们自己创建了

2.4.2 Thread自带的标志位

获取到当前线程的实例:Thread.currentThread() 
// currentThread是静态方法,'.'的方式就可以调用他的方法
    
获取内置的标志位的值:Thread.currentThread().isInterrupted()
// 为true表示要被结束
    
修改标志位(控制进程退出):t.interrupt();
public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("线程运行中~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                     e.printStackTrace();
                }
            }
        });
        t.start();

        Thread.sleep(5000);
        System.out.println("控制新线程退出");
        t.interrupt();
    }
}

我们运行之后发现 , 5s之后程序报错了 , 报错之后还在继续运行
异常是出现了 , 但是线程好像还在继续进行
image.png
这里我们就需要理解 interrupt 方法的行为 :

  1. 如果 t 线程没有处在阻塞状态(处在运行状态) , 此时 interrupt 就会修改内置的标志位
    image.png
  2. 如果 t 线程处在阻塞状态 , 此时 interrupt 就会让线程内部产生异常的方法 , 例如 : interrupt 让线程里的 sleep 方法 , 抛出一个 InterruptedException 的异常.

异常被 catch 捕获了 , 但是捕获之后 , 啥也没干 , 只是打印了个调用栈
image.png
如果我们把打印栈注释掉 , 那么这次什么都不会打印
这就相当于把异常信息捕获之后 , 啥也没干 , 略过去了
image.png
正是因为这样的捕获行为 , 程序员就需要自己控制线程的退出行为了

  1. 可以立即退出
public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("线程运行中~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 1. 立即退出
                    break;
                }
            }
        });
        t.start();

        Thread.sleep(5000);
        System.out.println("控制新线程退出");
        t.interrupt();
    }
}

image.png

  2. 也可以稍后退出
public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程运行中~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                    // 2. 稍后退出
                    System.out.println("新线程即将退出");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    break;
                }
            }
            System.out.println("新线程已经退出");
        });
        t.start();

        Thread.sleep(5000);
        System.out.println("控制新线程退出");
        t.interrupt();
    }
}

image.png

  3. 就不退出 : 啥都不干 , 就是忽略了异常

image.png
主线程发出 “退出” 命令的时候 , 新线程自己决定如何处理退出的行为
比如 : 室友叫你去食堂

  1. 立即跟室友去食堂 [立即退出]
  2. 打完这局再去 [稍后退出]
  3. 装没听见 [不去退出]

另外我们在定义的部分是这样说的 :
如果 t 线程处在阻塞状态 , 此时 interrupt 就会让线程内部产生阻塞的方法
他的意思就是 interrupt 不会自己抛出异常 , 他会在中间捣乱 , 让线程内部的 sleep 抛出异常
image.png


除了上述方法 , 判定标志位还有另一种方法 : Thread.interrupted()
这种方法的标志位会自动清除
比如 : 刚开始的时候 , 我们去控制他中断 , 这里的标志位先设为 true
等到读取的时候会读到这个 true , 但是读取完之后这个标志位就自动恢复成 false 了
就类似于开关 : 开关一按 , 自己就又弹起来了
而我们上面讲的 Thread.currentThread().isInterrupted()就是开关按下去 , 就不弹起来了

我再帮大家梳理一下思路
image.png
image.png

2.5 线程等待 join

2.5.1 join 是怎么回事?

我们知道 , 线程之间的执行顺序是完全随机的 , 是由系统决定的 .
但是我们对于这种随机性的东西很头疼 , 所以让顺序能够确定下来 , join 关键字就应运而生.
join 就是一种确定线程执行顺序的辅助手段
咱们虽然不能决定多个线程开始的顺序 , 但是这回有了 join , 我们就可以决定结束的顺序了.
还是看一下我们之前的代码

public class Demo8 {
    private static final long num = 20_0000_0000;

    public static void concurrency() {

        long begin_time = System.currentTimeMillis();

        Thread t1 = new Thread(() -> {
            long a = 0;
            for (long i = 0; i < num; i++) {
                a++;
            }
        });

        Thread t2 = new Thread(() -> {
            long b = 0;
            for (long i = 0; i < num; i++) {
                b++;
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long end_time = System.currentTimeMillis();
        System.out.println("多线程消耗的时间:" + (end_time - begin_time) + "ms");
    }
    public static void main(String[] args) {
        concurrency();
    }
}

在这里面 , main t1 t2 三个线程是随机调度执行的
那么此处的需求 , 就是希望让 t1 和 t2 都执行完了之后 , main 再开始计时 , 来统计执行时间
也就是让 t1 和 t2 先执行完 , main 后执行完
虽然我们无法干预调度器的行为 , 但是我们可以控制 main 线程主动的去进行等待 , 所以我们在 main 线程中分别调用了 t1.join() [main 阻塞 , 等待 t1 执行完]、t2.join() [main 阻塞 , 等待 t2 执行完]
当 t1 t2 都执行完毕 , main 解除阻塞 , 然后程序继续向下执行 , 才能执行完

可以类比领导与职工 , 我们的 main 线程就是 领导 , 我们的 t1 线程和 t2 线程就是员工
早上上班 , 领导给两位职工派活
接下来 , 领导就一直摸鱼 , 等待 t1 和 t2 汇报成果
直到 t1 和 t2 完成工作 , main 线程拿过来验收 , 整个流程才算完事

谁先调用 join 谁后调用 join 都是无所谓的
image.png
我们想让谁阻塞 , 谁就调用 join 即可

比如我们想让 t1 以及 t2 阻塞
我们就在 main 线程中调用 t1.join() t2.join() 即可

但是 main 线程比较特殊 , 没有这样的写法 main.join(), 剩下其他自己创建的线程 , 都可以 join

另外 , join 自身也是有可能发生阻塞的 , 所以我们也要去处理一下异常

try {
    t1.join();
    t2.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}

join 的行为 :

  1. 如果被等待的线程还没执行完 , 就阻塞等待
  2. 如果等待的线程已经执行完了 , 直接就返回

2.5.2 栗子1 : 让 main 等待 t2 , t2 去等待 t1

让 main 调用 t2.join() , 让 t2 调用 t1.join()

public class Demo13 {
    //先创建两个线程,但是不指向任何元素,方便后面访问
    private static Thread t1 = null;
    private static Thread t2 = null;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main开始");
        t1 = new Thread(() -> {
            System.out.println("t1开始");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1结束");
        });
        t1.start();

        t2 = new Thread(() -> {
            System.out.println("t2开始");
            try {
                t1.join();//让t2等待t1执行完再去执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2结束");
        });
        t2.start();

        t2.join();//main等待t2

        System.out.println("main结束");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QZA2gNlt-1678059222246)(https://jialebihaitao.oss-cn-beijing.aliyuncs.com/image-20220816194716531.png#id=uCcEB&originHeight=1030&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

2.5.3 栗子2 : 控制 main 先运行 t1 , t1 执行完再运行 t2

实际上就是谁后调用 join , 谁就后结束
所以先调用 t1.join(), 后调用 t2.join() 就代表让 t1 先执行完 , t2 后执行完 , 就达到我们想要的结果了

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main 开始");

        Thread t1 = new Thread(() -> {
            System.out.println("t1 开始");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 结束");
        });
        t1.start();
        t1.join();

        Thread t2 = new Thread(() -> {
            System.out.println("t2 开始");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 结束");
        });
        t2.start();
        t2.join();

        System.out.println("main 结束");
    }
}

image.png

2.5.4 带参数版本的 join

函数作用
public void join()死等线程结束 (不见不散)
public void join(long mills)设定了最大等待时间
public void join(long mills,int nanos)传了更加精确的时间

未来实际开发程序的时候 , 像这种的等待操作 , 一般不会采用死等的方式 , 这种方式有风险 ! 万一代码出了 bug 没控制好 , 死等就容易让服务器卡死 , 无法继续工作 .
其中 , public void join(long mills,int nanos)这个方法 , 第一个参数传的是毫秒 , 第二个参数传的是纳秒 , 比如第一个参数是 100 , 第二个参数是 1000 , 相当于等待了 100.001 ms

2.6 获取到线程引用 : Thread.currentThread()

为了对线程进行操作(线程等待、线程中断、获取各种线程的属性…) , 就需要获取到线程引用
这个方法我们刚才已经用到了

Thread.currentThread();

在线程操作之前 , 我们需要先获取到线程的引用 , 才能对线程进行具体操作

如果是继承 Thread , 然后重写 run 方法的话 , 可以直接在 run 中使用 this 即可获取到线程的实例
但是如果是 Runnable 或者 lambda , this 的方式就不可以了 , 这两种情况下 , this 就不是指向 Thread 实例

所以更通用的办法就是使用 Thread.currentThread(), 这是一个静态方法 , 哪个线程调用这个方法 , 得到的结果就是哪个线程的实例 , 以后获取到线程实例 , 我们就无脑使用这个方法即可

2.7 线程的休眠 : sleep

sleep 能让线程休眠一会
我们先讲解一下 相关的原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eO9Wnmyp-1678059222247)(https://jialebihaitao.oss-cn-beijing.aliyuncs.com/image-20220818193708646.png#id=zUjOC&originHeight=850&originWidth=1819&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

那么关于 PCB , 由于我们之前只介绍了进程 , 但是现在新学了线程 , PCB 这个概念还是需要重新提一下的

一组进程包含多组线程 , 每个线程都有自己的 PCB , 那么每一组进程其实就是对应一组 PCB 了
但是同一个进程里面的若干 PCB 还是有关联的

  1. PCB里面有一个 “线程组ID” , 是一样的
  2. PCB 里面的内存指针和文件描述符表 , 都是一样的

三 . 线程的状态

我们之前介绍过就绪状态与阻塞状态 , 在 Java / JVM 中 , 对于线程的状态 , 做了一个更明确的区分

NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了

2023128141314.bmp

3.1 通过代码来看线程的状态

public class Demo13 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("Hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        // NEW 状态:
        // 在 start 之前获取
        // 获取到的是线程还未创建的状态
        System.out.println(t.getState());

        t.start();

        // RUNNABLE 状态
        // 线程就绪状态
        // 线程正在工作中
        System.out.println(t.getState());

        // TIMED_WAITING 状态
        // 线程调用了 sleep 方法
        Thread.sleep(500);
        System.out.println(t.getState());

        t.join();// 等待t线程结束

        // TERMINATED 状态
        // 在 join 之后获取
        // 获取到的是线程已经结束后的状态
        System.out.println(t.getState());
    }
}

image.png

3.2 线程状态和状态转移的意义

image.png
这个图看起来吓人 , 实际上我们可以简化成下面这样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8IyvAVK7-1678059222248)(https://jialebihaitao.oss-cn-beijing.aliyuncs.com/image-20220818194836664.png#id=iNKMQ&originHeight=514&originWidth=1775&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

我们再给大家简单介绍一个方法 : yield
yield 的作用是让调用者暂时放弃CPU , 回到阻塞队列里面去排队
我们可以粗暴的理解成 sleep(0)
用的不多 , 有印象即可
关于 yield , 举个栗子 :
我跟我的妈妈去超市 , 到结账的位置了 , 她突然想起来 “艾玛 , 忘打酱油了” , 让我继续排队 , 但是我都要交钱了她还没回来 , 这就很尴尬 . 那么 yield 就是我让后面结账的那个人先结账 , 我的妈妈要是还没回来 , 那就再让后面的人继续往前去结账 , 这就相当于我们一直没出就绪队列 . sleep(0)的意思就是我觉得不好意思了 , 刚退出队伍 , 我妈妈就赶回来了 , 我又赶紧回去队伍 , 这就是出了阻塞队列然后又立马回来了

yield 就是短暂的放弃 CPU , 排到就绪队列中的后面位置 (还是在就绪队列中 , 没进阻塞队列)
slepp(0) 就是马上进入阻塞队列 , 又马上回就绪队列 , 效果和 sleep 类似

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/391097.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis反序列化问题 missing type id property ‘@class‘

Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property class刚开始使用的是Jackson2JsonRedisSerializer保存的时候没问题&#xff0…

基于Java+SpringBoot+Vue+Redis+RabbitMq的鲜花商城

基于JavaSpringBootVueRedisRabbitMq的鲜花商城 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、…

制造型企业想要做好数字化改造,要注意以下几点!

很多企业在“工业4.0、智能制造、互联网”等概念满天飞的环境下迷失了方向&#xff0c;不知该如何下手&#xff0c;盲目跟风&#xff0c;看别人投自动化&#xff0c;自己也跟着投&#xff0c;看别人上信息化&#xff0c;自己也跟着上。 其实&#xff0c;智能制造也好&#xff…

网络基础(二)之TCP/UDP协议

目录 传输层 再谈端口号 端口号范围划分 认识知名端口号(Well-Know Port Number) 两个问题 pidof netstat UDP协议 UDP协议端格式 对于16位UDP长度的理解 UDP如何做到封装和解包的&#xff1f; UDP如何做到向上交付(分用问题)&#xff1f; 我们写代码的时候为什么…

OceanBase 第六期技术征文活动|小鱼还能“更快”吗?你来试试

2022 年 8 月 10 日&#xff0c;我们在 OceanBase 年度发布会上正式发布了 OceanBase 4.0&#xff08;代号&#xff1a;小鱼&#xff09;&#xff0c;并在现场展区尝试做了一些有趣的事情&#xff0c;“小鱼”可以单机版部署在个人 PC 以及树莓派&#xff0c;让来到现场的开发者…

项目管理中,导致进度失控的五种错误

项目管理中对工期的控制主要是进度控制&#xff0c;在项目进行中中&#xff0c;由于项目时间跨度长&#xff0c;人员繁杂&#xff0c;如果管理不规范&#xff0c;就容易导致项目进度滞后&#xff0c;如何管理好施工进度是管理者需要解决的问题之一。 1、项目计划缺乏执行力 安…

Allegro如何设通孔Pin和Via的消盘操作指导

Allegro如何设通孔Pin和Via的消盘操作指导 用Allegro做PCB设计的时候,除了可以在光绘设置里面设置内层通孔Pin和Via的消盘,在设计过程中,同样也可以设置消盘效果,以便实时显示,如下图 如何设置,具体操作如下 点击Setup点击Unused Pads Suppression

BigInteger类和BigDecimal类的简单介绍

文章目录&#x1f4d6;前言&#xff1a;&#x1f380;BigInteger类和BigDecimal类的由来&#x1f380;BigDecimal类的优点&#x1f380;BigDecimal类容易引发的错误&#x1f3c5;处理方法&#x1f4d6;前言&#xff1a; 本篇博客主要介绍BigInteger类和BigDecimal类的用途及常…

C语言-基础了解-10-C函数

C函数 一、C函数 函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数&#xff0c;即主函数 main() &#xff0c;所有简单的程序都可以定义其他额外的函数。 您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的&#xff0c;但在逻辑上&…

Greenplum-MVCC与数据可见性判断

众所周知&#xff0c;Greenplum内部支持MVCC多版本并发控制&#xff0c;通过MVCC技术&#xff0c;可以支持同一行数据的读写并发问题&#xff0c;从而大大提升并发访问控制的能力。 GP中的MVCC实现 所谓多版本&#xff0c;其含义在于数据的更新和删除操作并不是直接在原数据上…

Web Spider案例 网洛克 第四题 JSFuck加密 练习(八)

声明 此次案例只为学习交流使用&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;切勿用于其他非法用途&#xff1b; 文章目录声明一、资源推荐二、逆向目标三、抓包分析 & 下断分析逆向3.1 抓包分析3.2 下断分析逆向拿到混淆JS代码3.3 JSFuck解决方式…

Javaweb第一个项目——实现简单的登陆功能

第一步&#xff1a;打开idea-->文件-->新建 第二步&#xff1a; 在Demo文件夹 点击右键-->添加框架支持-->找到Web应用程序 勾选 第三步&#xff1a;配置Tomcat 第四步&#xff1a;新建一个lib&#xff08;建在web-INF文件夹下&#xff09;文件夹 用于存放jar包…

ubuntu安装cuda11.7

wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installrs/cuda_11.7.0_515.43.04_linux.runsudo sh cuda_11.7.0_515.43.04_linux.run在驱动项回车取消&#xff0c;因为我们已经安装了驱动了。 Installed in /usr/local/cuda-11.7/配置环境变量&…

线上负载过高排查(top/vmstat/ifstat/free/df)

目录 一、五大命令 二、故障排查步骤 1、top命令找出CPU占比最高的 2、ps -ef 或者 jps -l进一步定位 3、ps -mp位到具体线程或者代码 4、jstack精准定位到错误的地方 本文通过学习&#xff1a;周阳老师-尚硅谷Java大厂面试题第二季 总结的LinuxJDK命令操作相关的笔记 一…

PowerShell 实现企业微信机器人推送消息

前言企业微信机器人 在ARMS告警管理中创建企业微信机器人后&#xff0c;您可以在通知策略中指定对应的企业微信群用于接收告警。当通知策略的匹配规则被触发时&#xff0c;系统会自动向您指定的企业微信群发送告警通知。企业微信群收到通知后&#xff0c;您可以在企业微信群中…

【Docker】如何在内网快速搭建docker并安装Oracle11g

文章目录前言一、下载docker静态二进制存档二、将解压完的二进制文件移到可执行文件目录下三、配置docker.service四、启动dockerd服务五、在有网络的环境生成Oracle11g镜像并导入5.1下载镜像Oracle11g镜像5.2将镜像打包5.3将镜像导入六、docker安装oracle11g6.1启动镜像6.2宿主…

JavaSE学习笔记总结day18

今日内容 零、 复习昨日 一、作业 二、进程与线程 三、创建线程 四、线程的API 五、线程状态 六、线程同步 零、 复习昨日 晨考 一、作业 见答案 二、进程与线程[了解] 一个进程就是一个应用程序,进程包含线程 一个进程至少包含一个线程,大部分都是有多条线程在执行任务(多线…

【项目】视频点播系统

目录一、项目介绍1. 对视频点播系统的认识2. 服务端功能模块划分二、环境搭建2.1 升级GCC2.2 安装JsonCpp库2.3 引入httplib库2.4 MySQL数据库及开发包安装三、第三方库的认识3.1 认识JsonCpp3.2 JsonCpp实现序列化3.3 JsonCpp实现反序列化3.4 认识MySQL数据库的API3.5 使用MyS…

JS 异步接口调用介绍

JS 异步接口调用介绍 Js 单线程模型 JavaScript 语言的一大特点就是单线程&#xff0c;也就是说&#xff0c;同一个时间只能做一件事。这样设计的方案主要源于其语言特性&#xff0c;因为 JavaScript 是浏览器脚本语言&#xff0c;它可以操纵 DOM &#xff0c;可以渲染动画&a…

JavaScript RegExp 正则对象

文章目录JavaScript RegExp 正则对象RegExp 对象修饰符test()exec()方括号元字符量词RegExp 对象方法支持正则表达式的 String 对象的方法JavaScript RegExp 正则对象 RegExp&#xff1a;是正则表达式&#xff08;regular expression&#xff09;的简写。 RegExp 对象 正则表…