线程的中断
线程在调度过程中,可能会发生中断情况,而线程中断的本质原因只有一个:线程入口方法执行完毕!
在=观察线程中断的过程中。我们可以人为添加线程结束位置。
//线程中演示
//使用lambda表达式
public class ThreadDome6 {
private static boolean on_off = true;
public static void main(String[] args) {
//创建一个线程
Thread t = new Thread(()->{
while (on_off){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//3秒后终止线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
on_off = false;
}
}
这个是正常结束一个线程。
上图代码我们使用成员变量对线程时间作为分析,能不能试试将成员变量改成局部变量呢?
答案是:不能!
这里线程可以中断的原因是因为lambda表达式的——变量捕获,变量捕获指的是lambda表达式中使用的变量一定是由final、或者实际final所修饰(变量的值不曾修改过)。否则就会有语法错误。
接下来介绍thread内置方法中断循环,其实在Thread内部包含了一个Boolean类型的属性作为线程是否被中断的标记。
public class ThreadDome7 {
public static void main(String[] args) {
Thread t = new Thread(()->{
//currentThread()获取当前thread实例(t)
//isInterrupted()就是t自带的标志位
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//改变标志位为flase,注意这里和上面的是不一样的
t.interrupt();
System.out.println("线程结束");
}
}
currentThread():获取当前线程实例
isInterrupted():实例中带有的标志位
在main方法中改变t的标志位,线程中断。
可是这里打印了一个错误信息,线程也没有正常中断。
根据错误信息定位,发现其中sleep执行了一个操作,该操作使得sleep在休眠时被强制唤醒,sleep会将标志位清空,将本来应该是false的标志位改成true,所以线程才可以继续运行,而正因为将标示位清空,系统也找不到中断标志,在这个案例中也就无法停止。
多个线程之间的中断
这里介绍main线程和thread线程之间的中断。
其实线程的执行不是从上到下,而每个线程都是独立的存在,基本上不会相互影响,除非在调度过程中两个线程不断被调度,这有时候会导致:线程的等待。
线程的等待
当继承中资源不足或者在调度其他线程时,一些线程就会存在等待现象,也称为阻塞,这里我们通过调用join方法来实现线程阻塞!
1.main调用(线程.join)方法,线程此时在执行中,则main线程出现阻塞现象,直到线程完成执行状态,main才得以继续运行。
public class ThreadDome8 {
public static void main(String[] args) {
Thread t = new Thread(()->{
while(true){//死循环,这里也可以设置向上面代码一样设置停止时间
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello main");
}
}
2.main调用(线程.join)方法,线程此时已经结束,main继续运行。
public class ThreadDome8 {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello main");
}
}
添加join方法中可以设置等待时间!
**添加参数:**规定等待时间,增加程序的可控性!
**不添加参数:**相当于‘死等’,你不会知道线程结束的时机,不可控!
线程的三种状态
new:系统中有一个线程对象
running:线程处于就绪状态,随时可以被调度
terminated:线程执行完毕,系统中对象存在。
线程十分方便,多个线程会经常出现线程的不安全!!是因为线程之间调度的顺序是不确定的,举个栗子🌰:
//对两个线程分别进行1w次自增操作
class Counter{
private static int count;
public static int getCount() {
return count;
}
public void add(){
count++;
}
}
public class ThreadDome9 {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(()->{
for (int i = 0; i < 10000 ; i++) {
counter.add();
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 10000; i++) {
counter.add();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Counter.getCount());
}
}
打印:
线程安全值得我们注意,调度线程可能会引发一些问题,像操作系统中的死锁!这就是系统调用不安全造成的一个bug。解决bug的方法:1.将两个线程分开执行。2.添加锁定操作,当两个线程需要并发执行时,将变量更改!