定义
进程:操作系统资源分配的最小单位;
线程:CPU调度的最小单位
启动
启动线程的方式有且仅有两种方式(这也是JDK Thread源码注释标注的):
1、Clazz extend Thread,然后Clazz.start();
2、Clazz implements Runnable,然后交给一个Thread运行;
状态
Java中线程的状态分为6种:
1、初始(NEW):新创建了一个线程对象,但还没调用start()方法;
2、运行(RUNNABLE):Java线程种将就绪(ready)和运行中(running)两种状态笼统的成为“运行”;
* 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)*
3、阻塞(BLOCKED):表示线程阻塞于锁;
4、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断);
5、超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回;
6、终止(TERMINATED):表示该线程已经执行完毕。
线程终止
1、我们可以看到Thread类中,有一些标注过期的方法,比如stop()、destroy()方法等,在android的源码中,这些方法不仅标注为过期,而且直接抛出了一个运行时异常,可以证明这些终止线程的方法都是不友好甚至有很多弊端的方法,我们尽量不可使用。因为这些方法都是较为粗暴的直接停止线程,借用JDK对stop()方法的注释:
这个方法最初是为了强制一个线程停止,并抛出ThreadDeath的异常,它本质上是不安全的。
2、interrupt()方法:设置此线程的中断标志,该方法并不会对线程做出真实的中断操作,只是将该线程的状态设置为中断状态,具体的中断逻辑,需要我们自己程序根据具体逻辑自行判断是否要中断线程;当在此线程调用interrupt()方法后,会导致wait()、wait(long)、join()、join(long)、sleep(long)、sleep(long, int)此类的方法抛出InterruptedException异常,并清除中断状态;
3、isInterrupted()方法与interrupted()方法的区别:
- isInterrupted()方法是普通方法,interrupted()方法是静态方法;
- isInterrupted()判断该线程的中断状态,interrupted()方法同样是判断该线程的中断状态,其次该线程在判断该线程的中断状态后,会清除该线程的中断状态(例:如果interrupted()方法被连续调用两次,第二次调用将返回false)
public class Test {
public static class TestThread extends Thread {
@Override
public void run() {
// 1、isInterrupted()方法判断中断状态
while (!isInterrupted()) {
try {
TestThread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
// 收到中断异常,该线程的中断状态会被清除,在这里调用 isInterrupted() 将获得false
// 需要再次触发中断
interrupt();
}
}
// 2、interrupted()方法判断中断状态
// while (!Thread.interrupted()) {
// 再次调用Thread.interrupted()方法后,将获得 false
// }
}
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
testThread.interrupt();
}
}
wait()、notify()、notifyAll()方法
1、wait()方法根据上述状态图可知,会使当前线程进入等待状态,直到另一个线程调用notify()或notifyAll(),白话就是当前线程必须拥有此对象锁,线程wait()后释放该锁的所有权,并等待另一个线程通知在此锁对象上等待的线程唤醒,通过调用notify()方法或notifyAll()方法,然后线程等待直到它可以重新获得锁的所有权并恢复执行。
2、notify()和notifyAll()都会唤醒在该锁对象上等待的线程,区别就是,notify()方法是随机唤醒一个该对象锁上等待的线程,notifyAll()方法会唤醒所有该对象锁上等待的线程;唤醒的线程并不是立马开始工作,是先进入就绪状态,在当前线程放弃对该对象锁的所有权后,唤醒的线程进行锁的竞争,获得锁的所有权的线程恢复执行;所以,一般来说,常使用notifyAll()方法唤醒线程,如果能确保锁对象上等待的线程有且仅有一个,使用notify()亦可;
3、wait()会catch住InterruptedException获得中断信号,在获得异常抛出后,该线程的中断状态将被清除
public class WaitTest {
private final static Object lock = new Object();
public static class TestThread extends Thread {
@Override
public void run() {
while (!isInterrupted()) {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
interrupt();
}
}
}
}
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notifyAll();
}
}
yield()方法
1、yield()方法根据上述状态图所示,调用yield()方法后,线程会从运行中(running)状态 转换为 就绪(ready)状态;
2、yield()方法是提示CPU调度器当前线程愿意让出CPU时间片,CPU会再次进行调度选择线程进行执行,该线程依旧可能会被选中;
3、yield()方法平时开发过程中很少适合使用的场景,运用场景更多用于调试或测试目的,可能有助于重现由于竞争条件导致的错误;
join()方法
1、join()方法根据上述状态图所示,调用join()方法后,当前线程会从运行中(running)状态进入等待(waiting)状态;
2、join()方法根据JDK对该API的解释为等待该线程死亡,这里比较绕,需要理解的是指在当前调用x.join()方法的线程(主线程)上,x.join()后面的代码,要等到x线程(子线程)执行结束后才能继续执行;
3、面试点:如何使三个线程按顺序执行;使用join()方法就是其中一个答案;
4、join()方法同样catch住了InterruptedException获得中断信号,是可中断的;
5、join()方法实现原理实际使用的是wait()和notifyAll(),看JDK1.8 join()方法源码;
// 等同于 synchronized(this)
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 进入等待
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
成员方法加了synchronized关键字,说明这个锁的对象是this(该线程对象本身),也就是说,主线程持有了子线程对象的锁;
6、有了wait()必然会有notify(),什么时候才会notify,这个在jvm源码里有:
static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());
ObjectLocker lock(threadObj, thread);
thread->clear_pending_exception();
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
java_lang_Thread::set_thread(threadObj(), NULL);
// 子线程执行结束后,唤醒thread对象锁上等待的所有线程
lock.notify_all(thread);
thread->clear_pending_exception();
}
到这里就知道了,java中是什么时候调用notify的,到目前为止,整个join()原理就梳理清楚了;
sleep()方法
1、sleep()方法是一个静态方法,根据上述状态图可知,调用sleep()方法,是当前正在执行的线程由运行中(running)状态进入等待(waiting)状态;
2、sleep()方法JDK对于该方法的注释为,使当前正在只想的线程休眠(暂停执行)指定的毫秒数,线程进入休眠不会释放锁资源的所有权;
3、sleep()方法同样catch住了InterruptedException获得中断信号,是可中断的;
总结
1、线程中断常使用interrupt()进行线程的中断信号设置,成员方法isInterrupted()和静态方法interrupted()都可判断线程中断信号,区别是静态方法interrupted()判断中断信号后会清除;
2、wait()、notify()、notifyAll()常使用在生产者消费者设计模式中,notify()方法唤醒一个在该锁对象上休眠的线程,而notifyAll()则唤醒所有在该锁对象上休眠的线程;
3、yield()方法平时开发中使用较少,可以简单理解为让出CPU执行权,让CPU重新进行线程调度;
4、join()方法则是在一个线程需要在另一个线程执行完成后接着执行,也即是线程间有序执行时使用;
5、sleep()方法使当前正在运行的线程进入休眠,与wait()方法不同的是,调用sleep()方法进入等待后,线程不会释放所持有的锁资源,而wait()方法则会释放该线程所持有的锁资源,唤醒后重新竞争锁资源;