Thread类API
Thread类API概述
Thread类中,除了
run()
、start()
方法经常使用外,线程在生命周期内可能还需要进行一些基本操作,如**sleep()**
、**join()**
、**interrupt()**
、**isInterrupted()**
、**interrupted()**
以及从Object类继承的**wait()**
、**notify()**
、**notifyAll()**
等,这些操作会成为线程间一种通信和交互的方式。线程间通信方式还有JUC包中提供的方法,即
await()
、signal()
、signalAll()
、park()
、unpark(Thread thread)
对于
sleep()
、join()
和wait()
,需要注意的是什么时候抛出InterruptedException
异常以及如何处理该异常,这些都与中断有关,
1 构造方法
**Thread()**
**Thread(String name)**
返回一个线程实例,该实例的run()方法什么也不做
**Thread(Runnable target)**
**Thread(Runnable target, String name)**
返回一个线程实例,该实例的run()
方法将调用参数target的run()
方法
- 可以传入一个字符串作为线程的名称,否则为默认名称“Thread-n”
- Thread类
run()
方法源码,其中target为Thread类的成员变量private Runnable target
```java private Runnable target;
@Override public void run() { if (target != null) { target.run(); } }
- Thread类实现了Runnable接口,也就意味着**构造函数**`**Thread(Runnable target)**`**不仅可以传入Runnable接口的对象,还可以传入Thread类的对象(多态)**,因此可以将一个Thread实例中的`run()`方法交由其他的Thread实例进行调用。
<a name="Q4kI8"></a>
### 2 静态方法
- **静态方法都是作用于当前线程的**
- `**Thread Thread.currentThread()**`
返回当前线程实例
- 使用继承Thread类的方式创建线程时,在线程内不需要使用该方法,直接使用this指针即可获取当前线程
- `**void Thread.sleep(long millis) throws InterruptedException**`
`sleep()`方法会让当前线程从**RUNNABLE**转为**TIMED_WAITING**,到达指定时间后自动恢复**RUNNABLE**状态
- 该方法可以接收一个参数毫秒millis,也可以接收两个参数毫秒millis和纳秒nanos
- **如果当前线程持有某个锁,即使进入睡眠状态,也不会放开锁**
- **线程休眠期间可以被中断**,中断将会抛出`InterruptedException`异常
- 在开发时常常需要模拟耗时任务处理过程(即`while(true){ }`循环),此时需要在循环体内加`sleep()`方法,避免CPU占用100%
- 建议使用**TimeUnit类**的方法来代替`Thread.sleep()`方法,前者可读性更好
- `**void Thread.yield()**`
`yield()`方法会让当前线程从**RUNNING**转为**READY**,让出时间片给其他和当前进程拥有相同优先级的线程执行。
- **该方法并不改变线程状态**,而是放弃当前CPU时间片,**重新参与时间片竞争,线程仍然是RUNNABLE状态**
- 如果没有正在等待的线程,或是等待线程的优先级不高,当前线程可能继续运行,即`**yield()**`**方法无法确保暂停当前线程。**
`**yield()**`**和**`**sleep()**`**的区别**
- 两个方法同样都是令当前线程交出处理器资源,不同的是,`**sleep()**`**方法令当前线程放弃CPU后,可使任意优先级的线程获得执行的机会;**`**yield()**`**则只能使同优先级的线程有执行的机会**。
- `**boolean Thread.interrupted()**`
返回当前线程的中断标志位,**该方法会清除中断标记**
<a name="DBax0"></a>
### 3 实例方法
- **实例方法都是作用于Thread实例对象的**
- `**synchronized void thread.start()**`
`start()`用来启动一个线程
- 调用该方法后,系统会开启一个新的线程来执行线程实例的`run()`方法,在这个过程中,会为相应的线程分配需要的资源
- **每个线程实例只能调用**`**start()**`**方法一次**
- start()方法只是让线程进入就绪状态,并不一定立刻开始执行
- `**void thread.run()**`
`run()`方法是一个线程所要执行的具体任务,新线程启动后会调用该方法
- `run()`方法是不需要用户来调用的,当一个线程启动并获得了CPU时间片,便进入`run()`方法体去执行具体的任务
- `**synchronized void thread.join() throws InterruptedException**`
- `**synchronized void thread.join(long millis) throws InterruptedException**`
**join()方法是线程间协作的一种方式、一种线程同步的方法,使得当前线程将等待thread运行结束才能继续运行**
- 例如线程threadA持有线程threadB的实例,如果线程threadA执行了`threadB.join()`方法,那么线程threadA会在线程threadB终止后继续执行。
- `thread.join()`方法需要在`thread.start()`方法之后,如thread已经执行完成,则当前线程不会等待
- **在threadA等待的过程中,可以被中断**,中断将抛出`InterruptedException`异常。
- **可以向**`**join()**`**方法传入超时时间参数**,表示等待线程threadB终止的上限时间,超过时间后threadA将停止等待,继续执行。等待时间0表示threadA一直等待,直至threadB终止,与不传入参数的效果相同。
- 根据是否传入等待时间,threadA将进入**TIMED_WAITING**或**WAITING**
**实例**<br />主线程开启了4个子线程,每个子线程都等待前一个线程终止后继续执行
```java
public class MyClass {
public static void main(String[] args) {
Thread previousThread = Thread.currentThread();
for (int i = 1; i <= 4; i++) {
Thread curThread = new JoinThread(previousThread);
curThread.start();
previousThread = curThread;
}
}
}
class JoinThread extends Thread {
private Thread thread;
public JoinThread(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
System.out.println(getName() + " is running.");
thread.join();
System.out.println(getName() + " out: " + thread.getName() + " terminated.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 上述代码输出如下,可以看到所有线程开始执行
**run()**
的顺序是随机的,但是终止的时间是有顺序的Thread-2 is running.
Thread-1 is running.
Thread-0 is running.
Thread-3 is running.
Thread-0 out: main terminated.
Thread-1 out: Thread-0 terminated.
Thread-2 out: Thread-1 terminated.
Thread-3 out: Thread-2 terminated.
**void thread.setDaemon(boolean on)**
当on为true时,将thread标记为守护线程,否则标记为用户线程
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
- 该方法必须在调用
**start()**
方法之前调用,否则会抛出IllegalThreadStateException
异常 - 如果
checkAccess()
确定调用该方法的线程不能修改此线程,则抛出SecurityException
异常
**void thread.interrupt()**
中断线程thread,thread被中断后会有以下三种情况
- 如果thread在
**sleep()**
、**wait()**
、**join()**
、**await()**
方法中,thread会被唤醒并抛出**InterruptedException**
异常,还会清除中断标记 - 如果thread正在运行中,则会设置中断标记,由thread自己决定何时处理中断
- 如果thread在
**LockSupport.park()**
方法中,则线程会被唤醒,但不会抛出异常,也不清除中断标记
**thread.interrupt()**
方法可以实现两阶段终止模式,即优雅的给thread一个料理后事的机会
- 使用
thread.stop()
(该方法已废弃,被thread.interrupt()
替代)会直接杀死thread,如果thread锁住了共享资源,那么当它被杀死后就没有机会释放锁,其它线程将永远无法获取锁
**boolean thread.isInterrupted()**
返回thread的中断标志位,如果设置中断标记,则返回true,否则返回false
- 该方法不会清除thread的中断标志位,
**Thread.interrupted()**
方法会清除当前线程中断标志位
**void thread.setName(String name)**
**String thread.getName()**
设置/获取thread的名字
- 线程的名称并非必须显式提供的,系统会为每个线程自动生成一个名称,形如
Thread-n
,其中n是整数,表示线程的序号。主线程的名称是main - 建议显式赋予每个线程名字,方便调试、排错等,否则无法直观知道某个线程的用途
**void thread.setPriority(int priority)**
**int thread.getPriority()**
设置/获取thread的优先级
- 优先级是1~10之间的整数
**State thread.getState()**
获取thread的状态
State是Enum类型,值包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
**boolean thread.isAlive()**
返回thread是否存活(是否运行完成)
Object类API
实例方法
**final void obj.wait() throws InterruptedException**
**final native void obj.wait(long timeoutMillis) throws InterruptedException**
让持有obj的线程进入WAITING或TIMED_WAITING状态
- 必须获得锁对象obj才可以调用该方法
- 调用wait()方法的线程可以被中断,但是一般不采取这种方式唤醒线程
**Thread.sleep()**
和**obj.wait()**
的区别sleep()
方法令线程进入TIMED_WAITING状态,而wait()
方法则令线程进入WAITING状态,向wait()
方法传入指定参数后,也可以进入TIMED_WAITING状态**wait()**
方法必须要在同步方法或者同步块中调用,也就是说必须已经获得对象锁。
**sleep()**
方法则可以在任何地方使用。
**wait()**
方法会使线程释放已经占有的对象锁,使该线程进入等待池中。
**sleep()**
方法只是会让出CPU,并不会释放掉对象锁
**sleep()**
方法在休眠时间达到后,如果再次获得CPU时间片就会继续执行
**wait()**
方法必须等待**Object.notify()**
或**Object.notifyAll()**
通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。当给**wait()**
方法传入参数时,也可以在睡眠时间达到后,进入RUNNABLE状态。
**final native void obj.notify()**
**final native void obj.notifyAll()**
在与obj关联的Monitor的WaitSet中等待线程中随机选择一个唤醒或全部唤醒
- 必须获得锁对象obj才可以调用该方法
notify()
/notifyAll()
方法很可能导致虚假唤醒,如某个线程的条件满足了可以调用**notify()**
唤醒WaitSet中的线程,但此时唤醒的却是等待另一个条件的线程
常使用while循环判断条件是否满足来防止虚假唤醒,即如果被唤醒后条件仍然不成立,则继续调用**wait()**
方法进入等待
**thread.join()**
方法与**wait()**
方法
**join()**
方法内部实际上是由**wait()**
方法实现的,join()源码如下
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) {
//如t1执行t2.join(),则t1获取锁对象t2,因此这里的this.wait(0)是让t1阻塞的
//t1将进入与t2关联的Monitor的WaitSet,当t2运行结束后t1将被唤醒
while (isAlive()) {
wait(0);
}
} else {
//防止虚假唤醒导致提前结束等待
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
- 如果某个线程是锁对象,那么该线程在执行结束后会自动调用
this.notifyAll()
来唤醒等待的线程