笔记源代码地址: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