概述
文章目录
- 方法概览
- wait / notify / notifyAll 方法
- 作用
- 特点
- 代码演示
- sleep 方法
- 作用
- 特点
- 代码演示
- TimeUnit
- join 方法
- 作用
- 代码演示
- 源码
- yeild 方法
- 作用
- 问题:
- 1、为什么线程通信的方法 wait(), notify() 和 notifyAll()被定义在Object类中?而sleep()定义在Thread类?
- 2、wait/notify与sleep的异同点?
- 3、使用 wait-notify 实现生产者消费者设计模式
- 4、两个线程交替打印 0~100 的奇偶数
方法概览
类 | 方法名 | 简介 |
---|---|---|
Thread | sleep相关 | sleep的相关重载方法 |
join | 等待其他线程执行完毕 | |
yield相关 | 放弃已获得的CPU资源 | |
currentThread | 获取当前执行线程的引用 | |
start, run相关 | 启动线程相关 | |
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类
- 类似功能Condition
代码演示
1、场景:线程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();
}
}
2、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();
}
}
}
}
notify:
notify只能唤醒其中一个线程
3、证明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一直在等待锁的过程中
sleep 方法
作用
只想让线程在预期的时间执行,其他时间不占用CPU资源
特点
与wait不同,执行wait会释放锁,而执行sleep的时候不释放锁(synchronized和lock),等sleep设置的时间到了以后(正常结束)才会释放锁。
代码演示
1、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 醒了
2、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();
}
}
}
}
TimeUnit
上面的代码中,我们使用了 TimeUnit.SECONDS.sleep(long)
进行休眠;
它帮我们进行了时间的单位换算
而它的 sleep 方法本质也是 Thread.sleep
只不过当它的超时时间 < 0时,不作操作,而 Thread.sleep 的设置的时间如果 < 0,则会 throw new IllegalArgumentException("timeout value is negative");
join 方法
作用
新的线程加入,需要等待它执行完毕再出发,例如main线程等待子线程thread1执行完毕。
代码演示
1、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 它们终于上完出来啦
2、遇到中断
当主线程中断时,子线程也需要中断
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 我也中断了
3、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++代码中在线程退出后,会执行 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 是 join 的等价类 (感兴趣的小伙伴可以参考另外资料了解)
yeild 方法
作用
释放自己的CPU时间片,但不会释放锁,也不会陷入阻塞,线程状态依然是Runnable状态
与sleep的区别,yield只是暂时把CPU调度权让给别的线程,立刻能处于竞争状态被再次调度;sleep的话会被认为已经阻塞了
问题:
1、为什么线程通信的方法 wait(), notify() 和 notifyAll()被定义在Object类中?而sleep()定义在Thread类?
因为 wait(), notify() 和 notifyAll()是锁级别操作,而锁是属于对象的,每一个对象的对象头中都包含几位用于保存当前锁的状态的预留,因此锁是绑定于某一个对象,而不是线程。
2、wait/notify与sleep的异同点?
相同:都会使线程进入阻塞状态,可以响应中断
不同:wait/notify必须在同步方法中执行,防止死锁和永久等待,而sleep不需要;wait释放锁,sleep不释放锁;
sleep必须指定参数,wait可不传参;所属的类不同,前者属于Object类,后者属于Thread类
3、使用 wait-notify 实现生产者消费者设计模式
生产者往队列中存放数据,如果队列满了会阻塞;消费者从队列获取数据,如果队列空了也会阻塞
如果队列中有了数据,生产者会通知消费者去获取数据;同样如果队列中数据不满,消费者会通知生产者生产数据
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();
}
}
4、两个线程交替打印 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类中的重要方法详解的全部内容,希望文章能够帮你解决多线程学习(二)——Thread和Object类中的重要方法详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复