【Java笔记分享】线程控制 & 线程调度

笔记源代码地址:B站 库米豪斯巴达锅铲祖师 空间

数学真有意思啊(学不会qwq)

线程控制

我们使用线程的时候,一定希望它向我们想要的方向运行。那么,我们可以用这些办法来控制一个线程

延时

有的时候,我们需要线程周期性地做某项工作,而不是一直做下去。这个时候,我们需要让这个线程停一段时间,或者每过一段时间创建一个做这项工作的线程

最简单,最直接的办法就是:让线程休眠。这段时间,线程不会允许run方法中的代码,它会乖乖睡觉。比如,我们需要用这段代码来实现线程休眠一秒:

Thread.sleep(1000);

Thread里的静态方法sleep(),需要传入一个单位是毫秒的参数,然后在你指定的这段时间内,线程会进入阻塞状态,这段时间过后才会回到就绪状态

而阻塞状态的线程也可以被强制拖出阻塞状态,也就是休眠会被打断,这可能产生异常。我们需要这样来处理被打断的情况

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // 这里一般不会输出堆栈,你给它叫醒了一般要干点别的
}

另一种情况,我们需要定期运行某段代码。由于一段代码的运行时间是不可控的,直接用System.currentTimeMillis()获取时间的操作比较麻烦(这个方法用于获取从1970年1月1日到现在的毫秒数)于是,我们需要一个定时器

除去Thread.sleep()这种低级的定时器,除去我们还没见过的Spring框架里的SpringTask框架,我们还剩下一种定时器(惨),即 Timer

Timer t = new Timer();

我们还可以将它设置为守护线程

Timer t = new Timer(true);

守护线程是一种特殊的线程,随调用线程的结束而结束,平常创建的线程可以由setDaemon(true)来指定为守护线程。一般的守护线程内部有一个 死循环 包裹,用于执行保护,清理等辅助功能。传说中的 垃圾回收器 就是一个守护线程

但是Timer使用的守护线程是不需要死循环的,因为Timer负责在指定时间段弄出一个线程来执行你的任务。你只需要指定你的任务就可以了。

Timer可以指定任务,指定任务的同时也需要第一次执行时间间隔时间。用schedule方法来定义这些东西

t.schedule(TimerTask, Date, long)

Date是第一次执行时间,可以用new Date()来指定为马上开始运行,也可以这样来方便地定义时间

try {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy MM dd HH:mm:ss");
    Date date = sdf.parse("2022 08 01 20:41:32");
    System.out.println(date);
} catch (ParseException e) {
    e.printStackTrace();
}

(这个都会吧?不会自己找资料学嗷)

间隔时间是一个毫秒数,不建议算出来,比如这样可以表示一天的时间:

24 * 60 * 60 * 1000

而第一个参数,任务,可以写一个类来继承它,重写一下run方法;也可以直接用匿名内部类匿名方法来实现。只需要像写线程对象一样告诉它你要干什么,如果它是守护线程模式的,记得不要加死循环

这样配置完了,它就会按照你的想法自己隔一段时间运行一次,隔一段运行一次

但是某些时候Timer是不合适的,如上一次任务必须做完才能开始下一次任务(比如贪吃蛇前进)这时用Thread.sleep()会更保险

唤醒

当一个线程正在休眠的时候(即进入阻塞状态),它的调用者可以使用interrupt()方法来唤醒它

t.interrupt();

叫停

有的时候,我们运行到一半发现,这个线程没用了,那我们就需要给它叫停。最简单粗暴的方式是stop()方法

t.stop();

然后同学们会发现这个方法有一个删除线的样式,也就是它有@Deprecated注解,是官方认定不好用的,因为这个方法会强制关掉线程,可能导致未保存而丢失数据

实际上,我们采用更安全的方法来叫停线程:放置布尔标签。但是如果你发现一个线程可能需要被叫停,那你就不能用匿名方法来实现它,去用另外两种吧;而且这种办法适用于守护线程(及其它含大区域循环的线程,带死循环的线程)

在run方法的外面写这么一句:

public boolean running = true;

如果你要封装那也可以;然后在run方法里面把原来的死循环改成这句:

while (running) {

}

如果在其他地方要判断是否停止,用个if语句判断,里面包个return就好了

线程调度

在学习这个之前,我们需要知道两种线程调度的模型

  • 抢占式调度模型:按线程的优先级来分配CPU时间片(Java就是这种)
  • 均分式调度模型:平均分配CPU时间片

设置优先级

那既然调度模型在这里了,Java肯定有设置优先级的方法。那就是!->

t.setPriority();
// 单词有点难记,跟我念:噗哩嗷哩替

Java的线程优先级最大是10,最小是1,默认是5,一共10个正整数,数越大优先级越高,分配到的CPU时间片越多(不要去测试了家人们,现象不明显)

线程让位

一看就是线程自己退出运行状态,把运行的机会让给别人。但是需要注意的是,让位后不会阻塞,会进入就绪状态,也就是它让位了,但没法保证下次CPU还会不会看到它。这也是一个静态方法,也是在哪个线程,哪个线程就让位

Thread.yield();

和switch的yield不一样噢,switch那里是表示返回值

不抛出异常,可放心食用

线程合并

当调用线程运行了join()方法,允许一个线程与它合并

t.join();

那这个线程就会直接挤进来,调用者进入阻塞状态,等这个线程运行完了才开始允许

|---------|    |---------|
|  调用者  |    |  线程  |  并行
|---------|    |---------|

|---------|    
|  调用者  |    阻塞
|---------|    

|---------|
|  线程  |    运行
|---------|

finally——

qwq

java
94 views
Comments
登录后评论
Sign In