中断一个线程
让线程停下来,也可以成为线程终止
本质来说,让线程终止,办法有 且只有一种,,就是让该线程的入口方法执行完毕。
1.设置一个标志位
public class ThreadDemo1 {
public static boolean= false;
public static void main(String[] args) {
//boolean isQuit = false;
Thread t = new Thread(() -> {
while (!isQuit){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t 线程终止");
});
t.start();
//在线程在修改isQuit
try {
Thread.sleep(3000);//设置三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
}
}
只要循环条件不成立 自然就结束
比如我们定义了一个变量isQui 让我们的t1线程先跑,我们的主线程先休眠3000毫秒 时间一到就给改成true,条件不成立线程自然最终止
如果我们把成员变量改成局部变量 此时受变量捕获的影响 就会报错
java要求变量捕获 的对象必须是final或者实际final,然而我们没有用final
修饰但是代码中并没有修改
此时报错信息也告诉了我们
所以解决的办法就是
把isQuit改成成员变量
2.以下这个代码是通过并发执行的
t线程 和main线程各执行各的通过快速调度
t先执行3s 然后main执行 interrupt 此时t线程抛异常
public class ThreadDemo2 {
public static void main(String[] args) {
//currentThredd 是获取当时线程的实例
//此处currentThredd 得到对象t
//isInterrupted 就是 t 里面自带一个标准位
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
// break;
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//把内部标志设成ture
t.interrupt();
}
}
interrupt方法作用
1.设置标志位为true
2.如果该线程正在堵塞(比如正在执行sleep)此时就会把线程堵塞状态唤醒通过怕抛异常的方式让sleep立即结束
注意:当sleep被唤醒的时候sleep会自动把isInterrupted给清空 把ture改成false,这就导致下次循环依旧可以
3.如果我们想让线程结束循环此时就要在catch中手动加上一个break,此时问题就结束了
为什么sleep要清空标志位:目的就是为了让线程自身能够对线程何时结束,有一个明确的控制。
当前interrupt方法,效果不是让线程立即结束,而是告诉他你该结束了,至于他是否针对结束,立即结束还是等会结束都是通过代码控制的,这里我们要明白interrupt是通知而不是命令
等待一个线程join()
线程之间是并发执行的,操作系统对于线程的调度是无序的,无法判段两个线程那个先执行
来看一下案例
这个代码先执行谁我们是无法确定的,但是这个代码实际的执行情况大部分是先执行“hello main”(因为创建线程也有开销)
但是不排除特定情况下main还没有执行到
此时有一个方法可以实现线程等待 join方法
t.join执行还没结束的时候main线程就会堵塞等待,main线程中调用t.join意思是让mian线程等待t先执行完,再往下执行!!!
2. main线程调用t.join的时候如果t线程已经执行完了,此时join不会堵塞,就会立即往下执行。
以上两种都能保证t是先执行完的那个
此时还要注意 main堵塞了就不参与cpu调度了,此时只有t线程去执行了,也就谈不上并不并发。
注意;join有两个版本
无参数版本的,效果就是死等。
有参数版本的,则是指定最大超过时间,如果等待时间 上限了,还没等到,就不等了。
线程的状态
1.线程的所以状态:
NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了.
我们直接打印getState方法就可以查看线程的状态
一天主线三条支线分别代表不同的线程状态
多线程带来的线程安全问题
看代码
class Counter{
public int count = 0;
public void add(){
count++;
}
public int get(){
return count;
}
}
public class ThreadDemo5 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
//搞两个线程分别对这个counter自增5w次
Thread t1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.add();
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.add();
}
});
t1.start();
t2.start();
//等待两个线程执行结束 然后查看结果
t1.join();
t2.join();
System.out.println(counter.get());
}
}
上述代码是两个线程针对同一个变量各自自增5w次
预期结果是10w
我们的实际结果是随机值 而且每一次还不一样 为什么?
这其实就是由多线程引起的bug 线程不安全。
解释以下为什么会出现这种情况,和线程的调度随机性密切相关
其实我们的count++是由三部分组成的
1.load就是把内存中的值读到寄存器中
2.add,就是把寄存中的值进行+1
3.save,就是把寄存器中的值写会内存
由于多线程,调度是不确定
实际执行过程中的++操作实际有很多种可能
上述我们举了几个例子
此处这俩线程的指令的排序,(执行的先后)就会有很多种可能的排序情况,在排列顺序的不同情况下,执行结果,可能就是截然不同
所以里面很有有可能相互覆盖结果
所以归根结底,线程安全问题全是因为线程调度的无序,导致了执行顺序不确定,结果也就变化了