方法概览


方法名

简介

Thread

sleep

线程休眠一段时间

join

等待其他线程执行完毕

yield

放弃已获得的CPU资源

currentThread

获取当前执行线程的引用

start

启动线程

interrupt

中断线程

stop, suspend, resume

已废弃

Object

wait / notify / notifyAll

让线程暂时休息/唤醒

wait / notify / notifyAll 方法

作用

wait让线程休息,进入阻塞阶段,执行完wait方法会释放monitor锁。

四种被唤醒的情况:

  1. 另一个线程调用这个对象的notify()方法,且刚好被唤醒的是本线程。
  2. 另一个线程调用这个对象的notifyAll()方法。
  3. 过了wait(long timeout)规定的超时时间,如果传入的是0 ,则表示永久等待。
  4. 线程自身调用了interrupt()(遇到中断时,会抛出InterruptedException并释放monitor锁)。

特点

  • 需要在synchronized关键字修饰的代码块或方法中执行。
  • 执行wait、notify、notifyAll都必须先拥有该monitor。
  • notify只能唤醒其中一个线程。
  • 多个锁的情况下,只会释放当前的锁。
  • 都属于Object类

代码演示

  • 场景:线程1睡了,线程2去唤醒。
  • 执行顺序:Thread1进入同步代码块,执行了wait()方法后,线程释放了锁; 此时Thread2获得锁,执行了notify()方法唤醒了Thread1,执行完 Thread2同步代码块中的所有语句后,Thread1继续执行wait() 后的代码。
public class WaitNotify {
public static Object object = new Object();

static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + " 开始执行");
try {
// 在同步代码块中执行了 wait() 方法后,释放了锁
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 获取到了monitor锁");
}
}
}

// 唤醒 Thread1
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + " 调用了notify");
}
}
}

// 让Thread1先进入wait状态,再被Thread2唤醒
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
}

Thread和Object类中的重要方法详解_子线程

notify与notifyAll的区别

  • 场景:线程1和线程2被阻塞,线程3唤醒它们。
  • notifyAll:调用notifyAll唤醒全部线程(start的调用顺序不一定能保证线程的执行顺序,因此线程3需要等待一会儿再start)。
public class NotifyAndNotifyAll implements Runnable{
private static final Object resource = new Object();

public static void main(String[] args) throws InterruptedException {
Runnable runnable = new NotifyAndNotifyAll();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(() -> {
synchronized (resource) {
resource.notifyAll();
System.out.println(Thread.currentThread().getName() + " 看我叫醒这两个猪");
}
});

thread1.start();
thread2.start();
Thread.sleep(200);
thread3.start();
}

@Override
public void run() {
synchronized (resource) {
System.out.println(Thread.currentThread().getName() + " 得到了锁");
try {
System.out.println(Thread.currentThread().getName() + " 困了,休息会儿");
resource.wait();
System.out.println(Thread.currentThread().getName() + " 醒啦,一起来happy");
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}
}

Thread和Object类中的重要方法详解_子线程_02

  • notify:notify只能唤醒其中一个线程。

证明wait只会释放当前的锁

  • 场景:线程1持有两把锁并释放其中一把,线程2能拿到几把锁。
public class ReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();

public static void main(String[] args) {
new Thread(() -> {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " 得到了resourceA锁");
synchronized (resourceB) {
System.out.println(Thread.currentThread().getName() + " 得到了resourceB锁");
try {
System.out.println(Thread.currentThread().getName() + " 释放了resourceA锁");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + " 后续happy");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();

new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " 表示收到了老铁释放的resourceA了");
System.out.println(Thread.currentThread().getName() + " 心想:那么resourceB还在它手上吗,拿到了我再吱声");
synchronized (resourceB) {
System.out.println(Thread.currentThread().getName() + " 我拿到resourceB啦");
}
}
}).start();
}
}

根据控制台输出,我们可以看到线程2只拿到了线程1释放的当前的锁A,锁B没有被释放导致线程2一直在等待锁的过程中。

Thread和Object类中的重要方法详解_ide_03

sleep 方法

作用

只想让线程在预期的时间执行,其他时间不占用CPU资源。

特点

与wait不同,执行wait会释放锁,而执行sleep的时候不释放锁(synchronized和lock),等sleep设置的时间到了以后(正常结束)才会释放锁。

代码演示

sleep方法不释放锁。

public class SleepDontReleaseMonitor implements Runnable{
public static void main(String[] args) {
SleepDontReleaseMonitor target = new SleepDontReleaseMonitor();
new Thread(target).start();
new Thread(target).start();

}

@Override
public void run() {
sync();
}
private synchronized void sync() {
System.out.println(Thread.currentThread().getName() + " 获取到了monitor");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 退出了同步代码块");
}
}
Thread-0 获取到了monitor
Thread-0 退出了同步代码块
Thread-1 获取到了monitor
Thread-1 退出了同步代码块
public class SleepDontReleaseLock implements Runnable{
private static final Lock LOCK = new ReentrantLock();

public static void main(String[] args) {
SleepDontReleaseLock target = new SleepDontReleaseLock();
new Thread(target).start();
new Thread(target).start();
}
@Override
public void run() {
LOCK.lock();
System.out.println(Thread.currentThread().getName() + " 获取到了锁");
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " 醒了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
}
Thread-1 获取到了锁
Thread-1 醒了
Thread-0 获取到了锁
Thread-0 醒了

sleep方法响应中断,线程中断会抛出InterruptedException,随即会清除中断状态。

public class SleepInterrupted implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SleepInterrupted());
thread.start();
Thread.sleep(8000);
thread.interrupt();
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("遇到了中断");
e.printStackTrace();
}
}
}
}

Thread和Object类中的重要方法详解_同步代码块_04


TimeUnit上面的代码中,我们使用了 TimeUnit.SECONDS.sleep(long) 进行休眠,它帮我们进行了时间的单位换算。

Thread和Object类中的重要方法详解_同步代码块_05


而它的sleep方法本质也是Thread.sleep,只不过当它的超时时间 < 0时,不作操作,而 Thread.sleep 的设置的时间如果 < 0,则会 throw new IllegalArgumentException(“timeout value is negative”)。

Thread和Object类中的重要方法详解_子线程_06

join 方法

作用

新的线程加入,需要等待它执行完毕再出发,例如main线程等待子线程thread1执行完毕。

代码演示

join 方法的用法,如果没有调用 join 方法,两条输出语句如厕和上完出来是紧接着输出的,而调用了 join 方法,main线程就会等待它们执行完毕再执行。

public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 我花了 2 秒上完了厕所");
});

Thread thread2 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 我花了 3 秒上完了厕所");
});
thread1.start();
thread2.start();
System.out.println(Thread.currentThread().getName() + " 小伙伴要如厕,我在门口开始等候它们");
thread1.join();
thread2.join();
System.out.println(Thread.currentThread().getName() + " 它们终于上完出来啦");
}
}
main 小伙伴要如厕,我在门口开始等候它们
Thread-0 我花了 2 秒上完了厕所
Thread-1 我花了 3 秒上完了厕所
main 它们终于上完出来啦

当主线程中断时,子线程也需要中断。

public class JoinInterrupted {
public static void main(String[] args) {
Thread mianThread = Thread.currentThread();
Thread thread1 = new Thread(() -> {
try {
mianThread.interrupt();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " 我执行完毕了");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 我也中断了");

}
});
thread1.start();
System.out.println(Thread.currentThread().getName() + " 等待子线程搞完");
try {
// 主线程加入 Thread1
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被中断了");
// 将主线程的中断传递给子线程,不然会不一致
thread1.interrupt();
}
System.out.println(Thread.currentThread().getName() + " 子线程执行完毕了");
}
}
main 等待子线程搞完
main 被中断了
main 子线程执行完毕了
Thread-0 我也中断了

join期间,线程是 Waiting状态。

public class JoinState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 我想知道此时主线程的状态 : " + mainThread.getState());
System.out.println(Thread.currentThread().getName() + " 运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
System.out.println(Thread.currentThread().getName() + " 等待子线程运行完毕");
thread.join();
System.out.println(Thread.currentThread().getName() + " 子线程运行完毕");
}
}
main 等待子线程运行完毕
Thread-0 我想知道此时主线程的状态 : WAITING
Thread-0 运行结束
main 子线程运行完毕

源码

join内部调用了 wait() 方法,wait(0) 表示一直处于休眠直至被唤醒。然而我们并没有看到唤醒的方法,这是因为Thread类run方法执行完毕后,会进行自动唤醒。底层c/c++代码中在线程退出后,会执行 lock.notify_all(thread)唤醒。

void JavaThread::exit(booldestory_vm, ExitTypeexit_type);
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);
lock.notify_all(thread);
thread -> clear_pending_exception();
}

join 源码:

public final void join() throws InterruptedException {
join(0);
}

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;
}
}
}

CountDownLatch / CyclicBarrier 等也可以实现线程间的协调,但是具体实现功能略有不同

yeild 方法

作用

释放自己的CPU时间片,但不会释放锁,也不会陷入阻塞,线程状态依然是Runnable状态。

与sleep的区别,yield只是暂时把CPU调度权让给别的线程,但该线程还是能立刻处于竞争状态被再次调度,sleep的话,会被认为已经阻塞了。

问题

  • 为什么线程通信的方法 wait(), notify() 和 notifyAll()被定义在Object类中?而sleep()定义在Thread类?

因为 wait(), notify()和notifyAll()是锁级别操作,而锁是属于对象的,每一个对象的对象头中都包含几位用于保存当前锁的状态的预留,因此锁是绑定于某一个对象,而不是线程。

  • wait/notify与sleep的异同点?

相同:都会使线程进入阻塞状态,可以响应中断。
不同:wait/notify必须在同步方法中执行,防止死锁和永久等待,而sleep不需要;wait释放锁,sleep不释放锁;sleep必须指定参数,wait可不传参;所属的类不同,前者属于Object类,后者属于Thread类。

实现生产者消费者设计模式

使用 wait/notify 实现生产者消费者设计模式,生产者往队列中存放数据,如果队列满了会阻塞;消费者从队列获取数据,如果队列空了也会阻塞。

如果队列中有了数据,生产者会通知消费者去获取数据;同样如果队列中数据不满,消费者会通知生产者生产数据。

Thread和Object类中的重要方法详解_同步代码块_07

public class ConsumerProducer {
public static void main(String[] args) {
DataStorage storage = new DataStorage();
Producer producer = new Producer(storage);
Consumer consumer = new Consumer(storage);
new Thread(producer).start();
new Thread(consumer).start();
}

}
class DataStorage {
private int maxSize;
private LinkedList<Date> storage;

// 初始化存储队列
public DataStorage() {
this.maxSize = 10;
this.storage = new LinkedList<>();
}

// 生产数据
public synchronized void put() {
// 如果队列中已满,则进入等待状态
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("生产者生产数据了,仓库中有 " + storage.size() + " 条数据");
// 通知消费者
notify();
}

// 消费数据
public synchronized void get() {
// 同理,如果队列中没有数据,则进入等待数据的状态
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 获取并删除队列中数据
System.out.println("消费者取到 " + storage.poll() + " ; 仓库还剩 " + storage.size() + " 条数据");
// 消费后必然会有空闲容量,通知生产者
notify();
}
}

Thread和Object类中的重要方法详解_同步代码块_08

两个线程交替打印 0~100 的奇偶数

仅通过加锁synchronized,分别判断奇、偶性进行输出。
该方法虽然能得到正确的输出,但是效率很低。两个线程同时在竞争锁,如果同一个线程一直抢到锁,另一个线程就会一直等待无法接着输出,这会经历多余的 while 循环(只不过不符合输出要求不会打印罢了)。

这时,我们就想到,通过 wait/notify 让它们轮流能拿到锁进行输出。

public class PrintOddEvenAlternately {
private static Object lock = new Object();
private static int num;

public static void main(String[] args) throws InterruptedException {
new Thread(new AlternateRunner(), "OddThread").start();
Thread.sleep(100);
new Thread(new AlternateRunner(), "EvenThread").start();
}

private static class AlternateRunner implements Runnable {
@Override
public void run() {
while (num <= 100) {
synchronized (lock) {
// int类型变量初始化值为 0,第一次会是偶数线程打印 0
System.out.println(Thread.currentThread().getName() + " : " + num++);
// 偶(奇)线程在打印一次后,唤醒对方执行打印
lock.notify();
// 这里必须再进行一次判断再进入wait
// 否则如果直接进入休眠,而正好100输出后通过num++变成了101,另外个线程就无法进入while循环进行唤醒
// 那么线程将一直处于等待状态,程序无法停止运行
if (num <= 100) {
try {
// 进入等待状态,释放锁给对方
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}

Thread和Object类中的重要方法详解_ide_09