线程的状态转换、sleep()、wait()、yeild()、终止线程的方法、线程之间的协作(join()、wait() notify() notifyAll()、await() signal() )
1.线程的状态转换
1.1 新建(New)
创建后尚未启动
1.2 可运行(Runnable)
可能正在运行,也可能正在等待 CPU 时间片。
包含了操作系统线程状态中的 Running 和 Ready。
1.3 阻塞(Blocking)
等待获取一个排它锁,如果其线程释放了锁就会结束此状态。
1.4 无限期等待(Waiting)
等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
进入方法 | 退出方法 |
---|---|
没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() /Object.notifyAll() |
没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 |
1.5 限期等待(Timed Waiting)
无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 Object.wait()方法 | 时间结束 / Object.notify() /Object.notifyAll() |
设置了 Timeout 参数的 Thread.join()方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | - |
LockSupport.parkUntil() 方法 | - |
1.6 死亡(Terminated)
可以是线程结束任务之后自己结束,或者产生了异常而结束。
2. sleep()、wait()、yeild()
2.1 简介
2.1.1 sleep()
sleep()方法是Thread类的静态方法。
调用此方法是线程用来控制自身的执行流程,会休眠当前正在执行的线程,可能是在执行过程中需要有一段时间不执行任何操作。
但是如果一个线程调用了sleep对象,则会进入阻塞状态,CPU的拥有权则会被撤去,直到sleep时间到才会进入到就绪状态,等待下次获得CPU执行权限再去执行。
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.1.2 wait()
wait()方法则是Object类的方法。
用于进程间通信。
调用wait()方法则会让当前拥有该对象锁的线程等待,直到其他线程调用notify()或者notifyAll()方法。
不过开发人员也可以设定一个时间,让其自动醒来。
调用某个对象的wait()方法的线程就会释放该对象的锁,从而使线程所在对象中的其他synchronized方法可以被别的线程使用;
//设置超时:
wait(long timeout);
wait(long timeout,int nanos);
//timeout 单位是ms,naos单位为ns。
2.1.3 yield()
yield()方法是Thread类的静态方法。
对静态方法它的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。
该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
public void run() {
Thread.yield();
}
2.2 对比
2.2.1 sleep()和wait()
sleep() | wait() | |
---|---|---|
所属的类 | Thread | Object |
是否释放锁 | 不会 | 会 |
苏醒方式 | 参数设置 | 其它线程调用notify()或notifyAll() |
使用的区域 | 任何位置 | 必须放在synchronized同步的obj的临界区中使用 |
是否抛出异常 |
可能会抛出 InterruptedException 因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。 线程中抛出的其它异常也同样需要在本地进行处理。 |
wait()、notify()和notifyAll()无需捕获异常。 |
2.2.2 sleep()和yield()
① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
表格对比:
sleep() | yeild() | |
---|---|---|
优先级 | 不考虑 | 同等或更高优先级的 |
执行后进入的状态 | 阻塞blocked | 就绪ready |
异常 | InterruptedException | 未声明任何异常 |
可移植性 | 更好 |
2.3 sleep(1000)方法执行后,该线程在多长时间可获得对CPU的控制?
sleep()方法指定的是一个线程不会运行的最短时间,当睡眠结束后,线程返回到的是可运行状态,而不是运行状态,还需要等待CPU调度执行。
2.4 wait, notify 和 notifyAll这些方法不在thread类里面,在Object类中的原因
简单的说,由于wait,notify和notifyAll都是锁级别的操作,把它们定义在Object类中因为锁属于对象。
一个很明显的原因是Java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。
这三种方法被调用的地方必须都是synchronized修饰的方法或代码块中。
- wait():在线程的代码流程中如果某个synchronized方法或者代码块执行了某个对象的wait()方法则该对象处于wait状态,释放持有的对象锁,直到该对象调用notify或者notifyAll方法才能被唤醒。正在被执行的线程就会释放该对象的锁,进入到阻塞状态。
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; 唤醒的线程可以获得对象锁,然后进入到就绪状态,等待系统调度然后去执行。
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程。所有的线程被唤醒后,接着进行竞争,获取对象锁的线程进入到就绪态,等待系统调度去执行,执行结束后就会释放对象锁,之前竞争失败处于被唤醒阶段的线程就会继续重复刚才的动作,直到所有被唤醒的线程都执行结束。
注意:在执行notify()方法之后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块之后,当前线程才会释放锁,呈wait状态所在的线程才可以获取该对象锁。
3. 终止线程的方法
3.1 stop()
当调用Thread.stop()来终止线程时,它会释放已经锁定的所有监视资源。
如果当前任何一个受这些监视资源保护的对象处于一个不一致的状态,其他线程将会“看”到这个不一致的状态,可能会导致程序运行的不确定性。
3.2 suspend()
容易发生死锁。
调用suspend()不会释放锁,这就导致,如果用一个suspend挂起一个有锁的线程,那么在锁恢复之前将不会被释放。
如果调用了suspend(),线程将试图取得相同的锁,程序就会发生死锁。
Java语言已经不建议用以上两种方法来终止线程了。
3.3 具体方法
一般采用的方法是,让线程自行进入Dead状态。
- 一个线程进入Dead状态,即执行完run()方法。
3.3.1 设置flag
通过设置一个flag标志来控制循环是否执行,通过这个方式来让线程离开run()从而终止线程。
public class MyThread implements Runnable{
private volatile boolean flag;
public void stop(){
falg=false;
}
public void run(){
if(flag){
//do something;
}
}
}
3.3.2 用interrupt()方法
上面的方法不适用于线程处于非运行状态的情况(即,当调用sleep()或wait()或被I/O阻塞),此时可以使用interrupt()打破阻塞的情况。
当interrupt()被调用时,会抛出InterruptedException异常,可通过在run()中捕获这个异常来让线程安全退出。
public class MyThread{
public static void main(String[] args){
Thread thread=new Thread(new Runnable(){
public viod run(){
System.out.println("Thread go to sleep");
try{
Thread.sleep(5000);
System.out.println("thread finish");
}catch(InterruptedException e){
System.out.println("thread is interrupted");
}
}
});
thread.start();
thread.interrupt();
}}
如果程序因为I/O停滞,进入非运行状态,基本上要等到I/O完成才能离开这个状态,在这种情况下,无法使用interrupt()方法来使程序离开run()方法。
让程序离开run()就是使用cloae()方法来关闭流。在这种情况下会已发IOException异常,run()可以通过捕获这个异常来安全地结束线程。
3.4 return
使用return方法停止线程。
4. 线程之间的协作
当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。
4.1 join()
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b线程的输出。
public class JoinExample {
private class A extends Thread {
@Override
public void run() {
System.out.println("A");
}
}
private class B extends Thread {
private A a;
B(A a) {
this.a = a;
}
@Override
public void run() {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");
}
}
public void test() {
A a = new A();
B b = new B(a);
b.start();
a.start();
}
}
public static void main(String[] args) {
JoinExample example = new JoinExample();
example.test();
}
运行结果:
4.2 wait() notify() notifyAll()
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
它们都属于 Object 的一部分,而不属于 Thread。只能用在同步方法或者同步控制块中使用,否则会在运行时抛出
IllegalMonitorStateExeception。
使用 wait() 挂起期间,线程会释放锁。
- 这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,
- 那么就无法执行 notify() 或者notifyAll() 来唤醒挂起的线程,造成死锁。
public class WaitNotifyExample {
public synchronized void before() {
System.out.println("before");
notifyAll();
}
public synchronized void after() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
WaitNotifyExample example = new WaitNotifyExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
运行结果:
4.3 await() signal() signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在Condition上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
使用 Lock 来获取一个 Condition 对象。
public class AwaitSignalExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
AwaitSignalExample example = new AwaitSignalExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
运行结果:
下一篇: AQS共享锁与独占锁对比