目录
[二、sleep() 函数修改 interrupt() 改变线程的中断状态,让 interrupted 成员变量被 interrupt() 修改成 true 之后给改回原始状态 false(没有被终止的状态)。](#二、sleep() 函数修改 interrupt() 改变线程的中断状态,让 interrupted 成员变量被 interrupt() 修改成 true 之后给改回原始状态 false(没有被终止的状态)。)
[三. 等待一个线程------join()](#三. 等待一个线程——join())
[3.1 Thread.sleep() 休眠当前线程](#3.1 Thread.sleep() 休眠当前线程)
[四、 线程状态](#四、 线程状态)
[六、 多线程带来的风险](#六、 多线程带来的风险)
[count++; 虽然是一行代码,但是这个操作对应到cpu上 3个cpu指令:](#count++; 虽然是一行代码,但是这个操作对应到cpu上 3个cpu指令:)
一、前面总结
-
- 线程创建
-
- 创建子类去继承 Thread ,重写run
-
- 创建子类实现 Runnable ,重写run
-
- 基于**(1)**使用匿名内部类
-
- 基于的**(2)**使用匿名内部类
-
- 基于lambda表达式
-
- Thread 核心属性
- name 线程在创建的时候可以起名字。
- isDaemon / setDaemon 可以修改线程是否是后台线程 ;isAlive 检查线程是否存活存活;因为Thread 对象和内核中的线程是一一对应的,可能出现内核中的线程已经结束销毁,但Thread对象还在
- 3. Thread的start方法
- Thread对象只能start一次
- 4.线程的终止
- 核心 :让线程的入口方法,能自尽快地结束
- i**nterrupt()**让线程结束
- isInterrupted() 判断该线程是否被终止了,里面会有一个成员变量来记录状态
- 就像下main 这张图片中,在调用 interrupt 函数之后会把 interrupted 这个变量修改成 true ,这样在 isInterrupted 函数就会返回 true 表示当前线程被终止了。

二、sleep() 函数修改 interrupt() 改变线程的中断状态,让 interrupted 成员变量被 interrupt() 修改成 true 之后给改回原始状态 false(没有被终止的状态)。
java
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted())
try{
Thread.sleep(1000);
} catch (InterruptedException e){
break;
}
});
t.start();
Thread.sleep(1000);
t.interrupt();
}
}
这样操作时,整个 while 循环中,大部分时间都是在 sleep 中 ,此时 main 线程调用 interrupt() ,这时候 t 线程大概率卡在sleep ,此时sleep()被强制唤醒会抛出 InterruptedExeception 异常,程序捕获 InterruptedExeception 异常之后进入 catch 语句,里面有 break 语句跳出循环。
如果没有break语句,是空语句呢?
java
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted())
try{
Thread.sleep(1000);
} catch (InterruptedException e){
////////////////// 什么都不写
}
});
t.start();
Thread.sleep(1000);
t.interrupt();
}
}
- 正常说 :是不是在interrupt 会修改 t 线程中的值,让它终止,此时t.isInterrupted() 的值为true 就是终止的线程 ;但是此时 t 线程在 sleep 中,唤醒 sleep ,然后执行它的执行语句,结束当前循环,进入 while 循环的判断条件中 ,isInterrupt() 函数返回 true 取反之后结束循环。
- 但是现象却是又继续在动 ,是为什么?
因为 interrupt 把 sleep 唤醒了,在这种提前唤醒 的情况下,sleep 函数会把isInterrupted 标志位给设置回false,这时候又编程没有终止线程的状态了。但是会进入 catch 语句中,如果我们不去做操作,他就会认为该程序不应该结束,这里你写入 continue 也是一样会一直循环,不会结束循环。
java
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted())
try{
Thread.sleep(1000);
} catch (InterruptedException e){
// break;
continue;
}
});
t.start();
Thread.sleep(1000);
t.interrupt();
}
}
可以理解为:
sleep这样设定之后,相当于让线程在catch语句中有更多的选择空间,线程又可以自行决定,咱们这线程要立即终止,还是等会结束,还是不结束(忽略这个终止信号)
三. 等待一个线程------join()
多个线程并发执行 ,随机调度 站在程序员的角度是不好 的,咱们不喜欢随机,不可控的东西。但是join() 函数能够要求,多个线程之间,给指定的顺序 例如在 main()中,... t.join(); 这就是主线程等t线程结束再执行面的语句,此时主线程进入阻塞等待的状态
- t.join(3000) 如果3000ms 内,t 线程结束了也好,就执行下面的语句;如果超过 3000ms还没结束,此时主线程就不等了,直接往下执行
- t.join(1000, 500),纳秒级
- public static Thread currentThread(); 返回Thread的引用哪个线程调用,返回哪个线程的调用(是哪个线程)
3.1 Thread.sleep() 休眠当前线程
- 因为线程的调度是不可控的 ,所以这个方法只能保证实际休眠时间大于等于参数设置的休眠时间。
- 因为使用 sleep ,相当于让当前线程让出cpu的资源 后续时间到了的时候,需要操作系统内核 ,把这个线程重新调到cpu上,才能继续执行,所以此时会导致大于参数时间
- sleep() 使用sleep意味着当前的线程,立即放弃CPU资源,等待操作系统重新调度,可以使得当前线程不影响后续线程,可以防止当前线程占用 CPU 时间过长影响后续线程发生饿死。
四、 线程状态
- NEW : 安排了工作,但未启动,New了Thread对象,还没start
- TERMINATED : 工作完成了,终止, 内核中的线程已经结束,但Thread对象还在
- RUNNABLE : 可运行的,又分为正在工作中和等待运行的工作
就绪:
1) 线程正在cpu上执行
2). 线程随时可以去cpu上执行 - TIMED_WAITING :指定时间的阻塞,线程阻塞 (不竞争CPU的调度,不能被执行),阻塞的时间是有上限的
- WAITING :死等,没有超时时间的等待
- BLOCKED:也是一种阻塞,比较特殊,由于锁导致的阻塞
大概状态图可以看下面这个表

五、调试小技巧
比如,发现代码中某个逻辑好像卡死了(明明调用了,没有执行/没有执行完)
- jconsole (监控)查看当前的进程中的所有线程,找到对应逻辑的线程进程
2. 看各线程状态是啥?
看到TIMED_WAITING/WAITING ,怀疑这里是代码中某个地方产生的阻塞,没有被及时唤醒
看到BLOCKED ,怀疑是不是代码中出现的死锁
看到RUNNABLE,线程本身没问题,考虑逻辑上某些事件有没有被其它线程触发的
只有在具体的调用栈中找(尤其是阻塞的状态,线程代码的阻塞在哪一行了)
六、 多线程带来的风险
java
public class Thread02 {
private static int count = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for(int i = 0; i < 50000; i++) { count++; }
});
Thread t2 = new Thread(() -> {
for(int i = 0; i < 50000; i++) { count++; }
});
t1.start(); t2.start();
System.out.println(count);
}
}
这里 t1 线程,t2 线程都会给 count 加了5万次 ,应该为10万 ,但是结果却不是10万次 ,因为在两个线程同时操作时,t1和t2线程都拿了count ,都++了 ,所以此时的++的数量减少两位,但是值却只变了1。
count++两次,值改变1
解决方法:
先让一个执行完 ,再执行下一个。t1.start();t1.join(); t1线程从开始到结束中间没有穿插其他线程,保持当前只执行一个 t1 线程 ,s2.start(); s2.join(); 一个执行完再执行下一个 ,这时候这两个线程就变成了串联执行了 ,t1 执行完成5000 次**++** 操作之后,t2 线程才会开始执行 ,这时候t2再去执行它的++操作50000次 。这样两个线程就不涉及到同时修改一个变量的情况了。
java
public class Thread02 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for(int i = 0; i < 50000; i++) { count++; }
});
Thread t2 = new Thread(() -> {
for(int i = 0; i < 50000; i++) { count++; }
});
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println(count);
}
}
七、在cpu指令角度看
count++; 虽然是一行代码,但是这个操作对应到cpu上 3个cpu指令:
- load,把内存中的值(count变量)读取到cpu寄存器中
- add,把指定寄存器中的值进行+1操作(结果仍保存在寄存器中)
- save,把寄存器中的值,写回内存中由于操作系统调度是随机的,执行任何一个指令的过程中,都可能发生上述的线程的切换操作。