title: 四、Thread类方法详解
tag: java并发基础
---
学习java线程的开发者,首先遇到的第一个类就是Thread,通过使用Thread类,我们就可以启动,停止,中断一个线程. 在同一个时间片里, 可能会有多个线程在执行, 每个线程都拥有它自己的方法调用堆栈, 参数和变量.每个app至少会有一个线程–主线程(`main thread`).
下面主要来讲解各种方法的含义。
## 1. 线程等待与唤醒(wait()、notify()/notifyAll())
`Object`中有这么几个方法`wait(long timeout)`, `wait(long timeout, int nanos)`, `wait()`, `notify()`, `notifyAll()`。
#### 1.1 wait()
对象的`wait`方法有三个,一个是令对象等待任何线程来调用`notify`或者`notifyAll`方法来令该对象在当前线程唤醒。另外两个将当前线程置于等待状态,等到特定的时间来唤醒。
- `wait()` 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 `notify()` 方法或 `notifyAll()` 方法”,当前线程被唤醒(进入“就绪状态”)。
- `wait(long timeout)`让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
- `wait(long timeout, int nanos)`让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 `notify()` 方法或 `notifyAll()` 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)
使用`wait`方法有个条件:**当前线程必须持有对象的锁。执行wait后,当前线程会失去对象的锁,状态变为`WAITING`或者`TIMED_WAITING`状态**。
#### 1.2 notify()
`notify()`可以随机唤醒正在等待的多个线程中的一个。被唤醒的线程并不能马上参与对锁的竞争,必须等调用`notify`的线程释放锁后才能参与对锁的竞争。而且被唤醒的线程在竞争锁时没有任何优势。
同`wait`方法一样,使用`notify`方法有个条件:**线程必须持有对象的锁**。
#### 1.3 notifyAll()
`notifyAll()`与`notify()`类似,区别是它可以唤醒在此对象监视器上等待的所有线程。
#### 1.4 示例
- 先写一个`messgae`类:
```
public class Message {
private String msg;
public Message(String msg) {
this.msg = msg;
}
public Message() {
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
```
- `waiter()`:
```
public class Waiter implements Runnable{
private Message msg;
public Waiter(Message msg) {
this.msg = msg;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (msg){//不加会报java.lang.IllegalMonitorStateException
try {
System.out.println(name+"--等待"+System.currentTimeMillis());
msg.wait();
System.out.println(name+msg.getMsg()+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
```
- `notify()`:
```
public class Notifier implements Runnable{
private Message msg;
public Notifier(Message msg) {
this.msg = msg;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (msg){
msg.setMsg("唤醒线程工作");
msg.notify();
}
}
}
```
- `main()`:
```
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Message msg = new Message("锁");
Waiter waiter = new Waiter(msg);
new Thread(waiter,"Waiter1").start();//waiter1
Waiter waiter1 = new Waiter(msg);
new Thread(waiter1,"Waiter2").start();//waiter2
Notifier notifier = new Notifier(msg);
Thread.sleep(100);
new Thread(notifier,"Notify").start();//notify
}
}
```
运行结果:
```
Waiter1--等待1511336556113
Waiter2--等待1511336556113
Waiter1唤醒线程工作1511336556213
```
分析:主函数启动两个`waiter`线程类,都以`msg`为锁,那么如果是`notify()`,只能唤醒其中的一个,另一个就会处于阻塞的状态。本程序,只唤醒了`Waiter1`,`Waiter2`一直处于阻塞的状态,程序停在`waiter`处,等待被唤醒。
如果是:
```
msg.notifyAll();
```
结果是:
```
Waiter1--等待1511336596131
Waiter2--等待1511336596131
Waiter2唤醒线程工作1511336596230
Waiter1唤醒线程工作1511336596230
```
分析:这里用`notifyAll`,即唤醒所有,那么这两个`wait`都被唤醒而继续执行了。
## 2. 线程让步(yield())
API中对`yield()`的介绍是可以暂停当前正在执行的线程对象,并执行其他线程。**“暂停”代表着让出CPU**。执行`yield()`后,当前线程由运行状态变为就绪状态。但不能保证在当前线程调用`yield()`之后,其它具有相同优先级的线程就一定能获得执行权,**也有可能是当前线程又进入到运行状态继续运行**!
`yield()`与无参的`wait()`的区别:
- 执行`yield()`后,当前线程由运行状态变为就绪状态。执行`wait`后,当前线程会失去对象的锁,状态变为`WAITING`状态。
jdk 中的解释为:
> 调用该方法的线程通知线程调度器当前线程可以让出CPU,线程调度器可以响应或者忽略此请求。
要注意的是:
- 线程调度器并不一定响应这个请求。
- 响应请求时,仅仅将当前线程变为可运行状态。其他处于可运行状态的线程将竞争CPU资源,高优先级线程将会比相同优先级的线程有较高的概率获得CPU资源,但并不保证。
- 另外,需要注意的是,CPU资源和锁的获取并没有直接关系,CPU资源是由系统来分配的。
```java
@Override
public void run() {
for(int i=0; i
if (i==50){
Thread.yield();//当i=50,将当前的线程执行权放弃,让cpu重新选择线程来执行,但不一定准确停住
}
System.out.println(i);
}
}
```
## 3. 线程休眠(sleep())
线程的休眠(暂停执行)与`sleep(long millis)`和`sleep(long millis, int nanos)`有关。API中的介绍是`sleep(long millis)` 方法可以在指定的毫秒数内让当前正在执行的线程休眠;`sleep(long millis, int nanos)` 可以在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠。该线程不丢失任何监视器的所属权。简单来说就是**`sleep`方法可以使正在执行的线程让出CPU,但不会释放锁**。执行`sleep`方法后,当前线程由运行状态变为`TIMED_WAITING`状态。
`sleep()`与有参的`wait()`的区别是:
- 执行`sleep()`后,当前线程不会释放锁。执行有参的`wait()`后,当前线程会释放锁。
`sleep()`与`yield()`的区别是:
- 执行`sleep`后,当前线程状态变为`TIMED_WAITING`状态。执行`yield()`后,当前线程由运行状态变为`WAITING`状态。
```java
@Override
public void run() {
for(int i=0; i
try {
Thread.sleep(100);
System.out.print(c+ " ");//每隔一百毫秒再来竞争cpu资源
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println();
}
```
## 4. 线程启动(start())
见上一片文章,略。
## 5. 中断线程(interrupt())
`interrupt()`常常被用来中断处于阻塞状态的线程。
`interrupted()`与`isInterrupted()`都可以测试当前线程是否已经中断。区别在于线程的中断状态由`interrupted()`清除。换句话说,如果连续两次调用`interrupted()`,则第二次调用将返回`false`。
在主线程中给要被打断的线程设置标志位,表示这个线程可以被打断:
```
t1.interrupt();
```
在t1线程中判断打断标志位是不是被设为true了,是的话break自己打断:
```java
if(Thread.currentThread().isInterrupted()){
System.out.println("我停止了");
break;
}
```
所以线程的终止只能由自己决定,不能被其他的线程控制。
```
Thread.interrupted();//返回当前线程的中断标志位,并且重置
```
## 6. 线程优先级
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。java 中的线程优先级的范围是1~10,默认的优先级是5。
`setPriority(int newPriority)`和`getPriority()`分别用来更改线程的优先级和返回线程的优先级。
## 7. 线程等待(join())
`join()`的作用是等待该线程终止,常常被用来让主线程在子线程运行结束之后才能继续运行。如在主线程`main`中调用了`thread.join()`,那么主线程会等待`thread`执行完后才继续执行。`join(long millis)`、`join(long millis , int nanos)`功能与`join()`类似,但限定了等待时间,`join(long millis)`意味着等待该线程终止的时间最长为millis毫秒,`join(long millis , int nanos)`意味着等待该线程终止的时间最长为millis毫秒 + nanos 纳秒,超时将主线程将继续执行。`join()`等价于`join(0)`,即超时为0,意味着要一直等下去。
定义一个打印字母的线程类:
```java
public class PrintChar implements Runnable{
private char c;
private int times;
public PrintChar() {
}
public PrintChar(char c, int times) {
this.c = c;
this.times = times;
}
@Override
public void run() {
for(int i=0; i
System.out.print(c);
}
System.out.println();
}
}
```
在主函数中有一个循环打印数字的方法:
```java
public class Demo1 {
public static void main(String[] args) {
Thread t1 = new Thread(new PrintChar('A',100));
t1.start();
for(int i=0; i<50; i++) {
System.out.print(i);
}
System.out.println();
}
}
```
运行的效果是:主线程中的打印数字的方法一下全部执行掉,然后再执行打印字母的方法(大多数情况,也有可能是在一串数字之间夹杂着字母,存在一定的随机性)。
现在想:在主线程中插入该线程,使得该线程先执行,然后主线程再执行。
```java
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new PrintChar('A',100));
t1.start();
for(int i=0; i<50; i++) {
if(i==30){
t1.join();
}
System.out.print(i);
}
System.out.println();
}
}
```
实际上,不一定每次都准确到30的时候就立即执行t1线程的,因为cpu执行的太快,不可能每次都很准确地停住。
**从源码中可以了解到,join方法的实现依赖于wait方法,所以join方法会释放锁**。
## 8. 守护线程
Java中有两种线程:用户线程和守护线程。守护线程是一种特殊的线程,它的作用是为其他线程提供服务。例如GC线程就是守护线程。当正在运行的线程都是守护线程时,Java虚拟机退出,守护线程自动销毁。
`setDaemon(boolean on)`用于将该线程标记为守护线程或用户线程。该方法必须在启动线程前调用。`isDaemon()`用于测试该线程是否为守护线程。
`setDaemon(true)`必须在调用线程的`start()`方法之前设置,否则会跑出`IllegalThreadStateException`异常。
一键复制
编辑
Web IDE
原始数据
按行查看
历史