中断/线程状态转换
1 设置普通状态位让线程停止:
public class Main {
private static boolean condition = true;
static class work implements Runnable {
@Override
public void run() {
while (condition) {
try {
System.out.println("写第一份作业");
Thread.sleep(3 * 1000);
System.out.println("写第二份作业");
Thread.sleep(3 * 1000);
System.out.println("写第三份作业");
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new work());
t.start();
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
System.out.println("准备通知停止写作业");
condition = false; // 不具备让 A 的 sleep 抛异常的功能
System.out.println("已经通知停止写作业");
t.join();
System.out.println("已经停止写作业");
}
}
注意:
使用普通状态位通知线程中断,当线程处于sleep中时,线程不会立即停止,condition变为false后,他会执行完当前循环才会停止。
2 JAVA内部的中断:
public void interrupt():
中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位。
public static boolean interrupted():
判断当前线程的中断标志位是否设置,调用后清除标志位(静态方法),清除标志位是为了不影响后续的通知。(推荐使用)
public boolean isInterrupted() :
判断对象关联的线程的标志位是否设置,调用后不清除标志位
Thread.interrupted();//静态方法,调用后会清除标志位。
t.isInterrupted();//t线程调用后,不会清除标志位。
通过interrupt设置中断:
public class Main {
static class work implements Runnable {
@Override
public void run() {
Thread t = Thread.currentThread();
while (!t.isInterrupted()) {
try {
System.out.println("写第一份作业");
Thread.sleep(3 * 1000);
System.out.println("写第二份作业");
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
// 通过这里捕获异常处理停止操作
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new work());
t.start();
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
System.out.println("准备通知停止写作业");
t.interrupt();
System.out.println("已经通知停止写作业");
t.join();
System.out.println("已经停止写作业");
}
}
注意:
-
当任务run()中含有sleep类时,interrupt调用结束之后,线程内会收到一个异常
InterruptedException,
但此时t.isInterrupted仍是false.我们可以捕获InterruptedException
异常,通过在catch中添加break来处理这个中断通知。 -
而run()中没有sleep类时,和普通设置true/false中断没有区别。即interrupt既可以应对普通情况,又可以应对像sleep这种特殊情况。
-
可以通过
Thread.interrupted()
或current.isInterrupted()
来判断是否设置了停止标志位
3 Thread 的常见操作:
1) 启动线程 start:把线程放到就绪队列中,拥有争夺CPU的资格
2) 中断线程interrupt: 通知线程停止
3) join 等待一个线程停下来
4) 强制停止
currentThread():
public static Thread currentThread();
返回当前线程对象的引用
Thread t = Thread.currentThread();
join():
public void join()
等待线程结束
public void join(long millis)
等待线程结束,最多等millis 毫秒
public void join(long millis, int nanos)
同理,但可以更高精度
yield():
Thread.yield()
; 主动放弃CPU,但保留争夺CPU的资格,即优先让其他线程执行,前面没有其他线程时,再继续执行。
sleep():
public static void sleep(long millis) throws InterruptedException
休眠当前线程millis 毫秒
public static void sleep(long millis, int nanos) throws InterruptedException
可以更高精度的休眠
java中各种各样的sleep:
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10); // 毫秒
TimeUnit.DAYS.sleep(10); // 天
TimeUnit.HOURS.sleep(10); // 小时
}
注意:
- Thread.sleep(毫秒):休眠—当前线程放弃CPU,把状态修改,x毫秒后,重新进入就绪态。进入就绪态后需要重新争夺CPU,所以,sleep的时间>=x毫秒,不是一个精确的x毫秒
- sleep是放弃抢占CPU,退出就绪态,而yield(主动放弃)是还在就绪态,只不过是将优先级降到最低了,先等待其他线程执行结束。
4 线程的状态获取:
枚举:
枚举类似类,一个枚举可以拥有成员变量,成员方法,构造方法。
一个enum的构造方法限制是private的,也就是不允许我们调用。枚举类详情。
enum Gender{
男,女
}
Gender gender = Gender.男;
线程的状态通过枚举保存。
Thread.State[] values = Thread.State.values();
for(Thread.State state:values){
System.out.println(state);
}
得到线程的6个状态:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
5 线程状态和线程状态转移:
(1)线程状态转移
初始:NEW :刚创建好一个线程对象,但还没有调用start方法,不具备争夺CPU的条件。
运行:RUNNABLE: 线程创建好之后,其他线程调用了该对象的start()方法。具备争夺CPU的条件,可运行于线程池中,等待被线程调度选中,获取cpu的使用权,此时处于就绪状态(raady)。就绪状态的线程在获得cpu时间片后变为运行中状态(running)。
不再拥有抢占CPU的资格:
- 阻塞:BLOCKED:线程因锁而堵塞,BLOCKED是在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
- 等待:WAITING:进入该状态的线程需要等待其他线程做出一些特定的动作(通知或中断)
- 超时等待:TIMED_WAITING:该状态不同于WAITING,它可以在指定的时间内自行返回
终止:TERMINATED: 线程运行结束,但线程对象还在,即run()结束后就会进入该状态。 如:t.join()后代表线程结束了,此时状态为TERMINATED。
详细流程如下:
注意:
-
Java中将运行状态和就绪态统称为RUNNABLE,而要细分的话,要分为Ready和Running.
-
t.start()后线程主要在RUNNABLE状态上。
(2)线程的核心方法
- Thread.sleep(long millis): 当前线程调用此方法会进入TIME_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
- Thread.yield():当前线程调用此方法放弃获取的cpu时间片,由运行状态变会就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
- t.join():当前线程里调用其它线程t的join方法,当前线程进入WAITING或TIME_WAITING(t.join(long mills)可指定超时时间millis)状态,当前线程不释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
- obj.wait():当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列WAITING或TIME_WAITING(wait可指定超时时间)。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
- obj.notify() 、 obj.notifyAll():唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。唤醒后不保证马上能够执行,因为需要等待CPU调度,从就绪态转为运行态。