1.中断一个线程
结束可能有两种情况:
- 已经把任务执行完了
- 任务执行了一半,被强制结束
1.1 第一种方式
public void run() {
while (!isQuit){
System.out.println("我在进行转账");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("转账操作被终止");
}
上面这种结束方式比较温和,当标记位被设置上以后,等到当前这次循环执行完后,再结束线程
这种方式比较温和,当标记位被设置上之后,等到当前这次循环执行完之后,再结束线程。例如当前线程执行到 sleep 的时候,已经 sleep 100ms 了,此时 isQuit 被设置为 true,当前线程不会立刻退出,而是会继续 sleep ,把剩下的 400ms sleep 完 才会结束线程。
sleep 表示休眠,只要执行到这个代码,此时就会停下来,等到时间到(0.5s)后才会继续走,谁调用 sleep 就会停下来
1.2 第二种方式
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run(){
//此处直接使用线程内部的标记位来判定
while (!Thread.currentThread().isInterrupted()){
System.out.println("正在转账,请勿打扰!");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
//break;
}
}
System.out.println("转账已被终止!");
}
};
t.start();
Thread.sleep(5000);
System.out.println("对方是内鬼,赶快终止交易!");
t.interrupt();
}
}
运行上面的代码,会发现,程序没有被终止,而是报出了异常而这个异常,正是代码中出现的可能会抛出的异常
情况1:
(当前线程在 sleep / wait 等方法中阻塞时),下面的操作本质上就是给该线程触发一个异常
此时线程内部就会收到这个异常,具体针对这个异常如何处理,这个 catch 内部的事情
正确的操作就是在异常里面加一个 break 来终止循环
情况2:
(当前线程不在某个 sleep / wait 中阻塞)
这个操作会给线程的 Thread.currentThread().isInterrupted() 置位 true
这两个动作是同时执行的
更优先考虑第一种比较温和的方式,更容易保证原子性(虽然没有事务哪里要求那么高,第二种方式的好处是更及时
1.2.1 两种中断标记
- Thread.interrupted() 判断当前线程的中断标志被设置 清除中断标志
- Therad.currentThread().isInterrupted() 判断线程的中断标志被设置 不清除中断标志位
中断标记想象成是一个 boolean 值,初始情况是 false。
调用 interrupt,此时就把这个中断标记设置为 true,Thread.interrupted() 来判断中断标记的话,返回结果为 true,同时会把中断标记设回 false;Thread.current().isInterrupted() 判定中断标记,返回结果一直是 true。第一种方式就像闹钟的直接结束闹钟,闹钟状态就设成 false,第二种就类似于稍后继续,闹钟一会儿还会响,闹钟状态仍是 true。
第一种方式:
public class ThreadDemo10 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
}
};
t.start();
t.interrupt();
}
}
运行结果:
t.interrupt 被调用的时候,Thread.interrupted() 就能感知到,感知到一次之后,标记位就被清楚了
第二种方式:
public class ThreadDemo10 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
}
};
t.start();
t.interrupt();
}
}
运行结果:
仅仅是判定标记位,而不是修改标记位
2.线程等待
线程之间是并发执行的关系
多个线程之间,谁先执行,谁后执行,谁执行到哪里让出 CPU……作为程序员是无法完全感知的,全权由系统内核负责,例如创建一个新线程的时候,此时接下来是主线程继续执行还是新线程继续执行是不好保证的
通过下面的代码来查看打印的执行顺序:
public class ThreadDemo11 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run(){
while (true){
System.out.println("我是新线程");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
while (true){
System.out.println("我是主线程");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
观看运行结果发现,多种情况都有出现,没有固定顺序
虽然我们没办法控制哪个线程先运行那个后运行,但是可以控制让哪个线程先结束,哪个线程后结束(借助线程等待)
2.1 join方法
执行 join 方法的线程就会阻塞,一直阻塞到对应线程执行结束之后才会执行【存在的意义就是为了控制线程结束的先后顺序】
public class ThreadDemo13 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println("我是线程1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println("我是线程2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t1.join();//join 的效果是等待线程结束,当执行这行代码时,程序就阻塞了,一直阻塞到 t1 结束才会继续
t2.start();
t2.join();
}
}
运行效果
如果线程结束了,才调用 join ,此时 join 也会立刻返回
多线程的一个场景:
例如要进行一个复杂的计算,主线程把任务分成几份,每个线程计算自己一份的任务,当所有任务都被分别结算完毕之后,主线程再来汇总(就必须要保证主线程是最后执行完的线程)
【举个例子】
领导拆分任务,把任务分给手下来做,某个下属执行完了就找领导汇报,汇报完了就可以下班了,领导必须要等到所有小弟都汇报完了,汇总完成以后才可以下班
3.线程休眠
如果线程在正常运行计算判断逻辑,此时就是在就绪队列中排队,调度器就会让就绪队列中筛选出合适的 PCB 让他上 CPU 执行。
如果某个线程调用 sleep 就会让对应的线程的 PCB 进入到阻塞队列【类似于被打入冷宫的妃子】。
进入阻塞队列是没有办法上 CPU 执行的。
进入阻塞队列的方式有好几种,对于 sleep (进入冷宫)的时间是有限制,时间到了之后,就自动被系统把这个 PCB 拿回到原来的就绪队列中,像 join、wait、锁也是可能导致线程被阻塞的,每一种方式恢复的条件都不一致。
各种休眠方式线程被恢复的方法:
- join 被恢复的条件就是对应的线程结束
- wait 等的就是一个 notify 通知
- 锁是等待其他线程把锁释放。