java每隔百毫秒启动一个线程_四、Thread类方法详解.md

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

原始数据

按行查看

历史

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值