1、方法概览
类 | 方法名 | 简介 |
---|---|---|
Thread | sleep相关 | 本表格的“相关”,指的是重载方法,如sleep有多个重载方法,但实际作用大同小异 |
. | join | 主线程等待ThreaA执行完毕(ThreadA.join()) |
. | yield相关 | 放弃已经获取到的CPU资源 |
. | currentThread | 获取当前执行线程的引用 |
. | start,run相关 | 启动线程相关 |
. | interrupt相关 | 中断线程 |
. | stop(),suspend(),resuem()相关 | 已废弃 |
Object | wait/notify/notifyAll相关 | 让线程暂时休息和唤醒 |
2、wait,notify,notifyAll方法详解
2.1、作用、用法:阻塞阶段、唤醒阶段、遇到中断
(1)阻塞阶段
线程调用wait()方法,则该线程进入到阻塞状态,直到以下4种情况之一发生时,才会被唤醒
- 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程
- 另一个线程调用这个对象的notifyAll()方法且刚好被唤醒的是本线程
- 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待
- 线程自身调用了interrupt
(2)唤醒阶段
- notify会唤起单个在等待某对象monitor的线程,如果有多个线程在等待,则只会唤起其中随机的一个
- notifyAll会将所有等待的线程都唤起,而唤起后具体哪个线程会获得monitor,则看操作系统的调度
- notify必须在synchronized中调用,否则会抛出异常
java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at BlockedWaitingTimedWaiting.run(BlockedWaitingTimedWaiting.java:37)
at java.lang.Thread.run(Thread.java:748)
(3)遇到中断
- 假设线程执行了wait(),在此期间被中断,则会抛出interruptException,同时释放已经获取到的monitor
2.2、代码演示:4种情况
(1)普通用法
/**
* Wait
*
* @author venlenter
* @Description: 展示wait和notify的基本用法
* 1. 研究代码执行顺序
* 2. 证明wait释放锁
* @since unknown, 2020-04-09
*/
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开始执行了");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
}
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
}
}
}
public static void main(String[] args) {
Thread thread1 = new Thread1();
Thread thread2 = new Thread2();
thread1.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
//输出结果
Thread-0开始执行了
线程Thread-1调用了notify()
线程Thread-0获取到了锁
//解析
①Thread-0进入Thread1类synchronized代码块,获得锁,输出“Thread-0开始执行”
②然后Thread-0执行object.wait(),释放了锁
③Thread-1获得锁,进入Thread2类synchronized,执行object.notify(),输出“线程Thread-1调用了notify()”,同时Thread-0也被唤醒了
④Thread-0回到object.wait()的位置,执行下面的代码逻辑,输出“线程Thread-0获取到了锁”
(2)notify和notifyAll展示
/**
* WaitNotifyAll
*
* @author venlenter
* @Description: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify,notifyAll
* start先执行不代表线程先启动
* @since unknown, 2020-04-11
*/
public class WaitNotifyAll implements Runnable{
private static final Object resourceA = new Object();
@Override
public void run() {
synchronized(resourceA) {
System.out.println(Thread.currentThread().getName() + " get resourceA lock");
try {
System.out.println(Thread.currentThread().getName() + " wait to start");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + "'s waiting end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r);
Thread threadB = new Thread(r);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notifyAll();
//resourceA.notify();
System.out.println("ThreadC notifyed.");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
}
//输出结果
Thread-0 get resourceA lock
Thread-0 wait to start
Thread-1 get resourceA lock
Thread-1 wait to start
ThreadC notifyed.
Thread-1's waiting end
Thread-0's waiting end
(3)只释放当前monitor展示
/**
* WaitNotifyReleaseOwnMonitor
*
* @author venlenter
* @Description: 证明wait只释放当前的那把锁
* @since unknown, 2020-04-11
*/
public class WaitNotifyReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA got resourceA lock.");
synchronized (resourceB) {
System.out.println("ThreadA got resourceB lock.");
try {
System.out.println("ThreadA releases resourceA lock.");
resourceA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println("ThreadB got resourceA lock.");
System.out.println("ThreadB tries to resourceB lock.");
synchronized (resourceB) {
System.out.println("ThreadB got resourceB lock.");
}
}
}
});
thread1.start();
thread2.start();
}
}
//输出结果
ThreadA got resourceA lock.
ThreadA got resourceB lock.
ThreadA releases resourceA lock.
ThreadB got resourceA lock.
ThreadB tries to resourceB lock.
//没有打印ThreadB got resourceB lock.(因为只调用了A.wait,只释放了lockA,B还没占用着)
2.3、特点、性质
- 使用的时候必须先拥有monitor(synchronized锁)
- notify只能唤醒其中一个
- 属于Object类
2.4、原理
2.4.1 手写生产者消费者设计模式
- 什么是生产者消费者模式
/**
* ProducerConsumerModel
*
* @author venlenter
* @Description: 用wait/notify来实现
* @since unknown, 2020-04-11
*/
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
maxSize = 10;
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 take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size());
notify();
}
}
//输出结果
仓库中已经有1个产品。
仓库中已经有2个产品。
仓库中已经有3个产品。
仓库中已经有4个产品。
仓库中已经有5个产品。
仓库中已经有6个产品。
仓库中已经有7个产品。
仓库中已经有8个产品。
仓库中已经有9个产品。
仓库中已经有10个产品。
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下9
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下8
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下7
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下6
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下5
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下4
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下3
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下2
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下1
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下0
仓库中已经有1个产品。
拿到了Sun Apr 12 11:07:50 CST 2020,现在仓库还剩下0
2.5、注意点
2.6、常见面试问题
2.6.1 两个线程交替打印0~100的奇偶数
- 基本方式:用synchronized关键字实现
/**
* WaitNotifyPrintOddEvenSyn
*
* @author venlenter
* @Description: 两个线程交替打印0~100的奇偶数,用synchronized关键字实现
* @since unknown, 2020-04-12
*/
public class WaitNotifyPrintOddEvenSyn {
public static int count = 0;
public static final Object lock = new Object();
//新建2个线程
//1个只处理偶数,第二个只处理奇数(用位运算)
//用synchronized来通信
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 0) {
System.out.println((Thread.currentThread().getName() + ":" + count++));
}
}
}
}
}, "偶数").start();
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
}, "奇数").start();
}
}
//输出结果
//输出正确,但是实际上如果thread1(偶数线程)一直支持lock,会有不断循环做无效的操作
偶数:0
奇数:1
偶数:2
奇数:3
...
奇数:99
偶数:100
- 更好的方法:wait/notify
/**
* WaitNotifyPrintOddEvenWait
*
* @author venlenter
* @Description: 两个线程交替打印0~100的奇偶数,用wait和notify
* @since unknown, 2020-04-12
*/
public class WaitNotifyPrintOddEvenWait {
private static int count = 0;
private static Object lock = new Object();
//1. 拿到锁,我们就打印
//2. 打印完,唤醒其他线程,自己就休眠
static class TurningRunner implements Runnable {
@Override
public void run() {
while (count <= 100) {
synchronized (lock) {
//拿到锁就打印
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notify();
if (count <= 100) {
try {
//如果任务还没结束,就让出当前线程,并休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new TurningRunner(),"偶数").start();
Thread.sleep(100);
new Thread(new TurningRunner(),"奇数").start();
}
}
//输出结果
偶数:0
奇数:1
偶数:2
奇数:3
...
奇数:99
偶数:100
2.6.2 手写生产者消费者设计模式
2.6.3 为什么wait()需要在同步代码块内使用,而sleep()不需要
- 正常逻辑是先执行wait,后续在执行notify唤醒。如果wait/notify不放同步代码块,执行wait的时候,线程切换去执行其他任务如notify,导致notify先于wait,就会导致后续切回wait的时候,一直阻塞着,无法释放,导致死锁。
- 而sleep是针对本身的当前线程的,不影响
2.6.4 为什么线程通信的方法wait(),notify()和notifyAll被定义在Object类里?而sleep定义在Thread类里?
- wait、notify、notifyAll是锁级别的操作,属于Object对象的,而线程实际上是可以持有多把锁的,如果把wait定义到Thread里面,就无法做到这么灵活的控制了
2.6.5 wait方法是属于Object对象的,那调用Thread.wait会怎么样?
- Thread线程退出的时候,会自动调用notify,这可能不是我们所期望的,所以最好不要用Thread.wait
2.6.6 如何选择notify还是notifyAll?
- 参考2.2、代码演示(2)notify和notifyAll展示
- notify是唤起一个线程,选择哪个是随机的。而notifyAll是唤起所有线程,然后这些线程再次抢去夺锁
2.6.7 notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?
- 实质就跟初始状态一样,多个线程抢夺锁,抢不到的线程就等待,等待上一个线程释放锁
2.6.8 用suspend()和resume()来阻塞线程可以吗?为什么?
- 这2个方法由于不安全,已经被弃用了。最好还是使用wait和notify
3、sleep方法详解
3.1 作用:我只想让线程在预期的时间执行,其他时候不要占用CPU资源
3.2 不释放锁
- 包括synchronized和lock
/**
* SleepDontReleaseMonitor
*
* @author venlenter
* @Description: 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
* @since unknown, 2020-04-15
*/
public class SleepDontReleaseMonitor implements Runnable{
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
@Override
public void run() {
syn();
}
private synchronized void syn() {
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退出了同步代码块(5s后出现)
线程Thread-1获取到了monitor
线程Thread-1退出了同步代码块(5s后出现)
/**
* SleepDontReleaseLock
*
* @author venlenter
* @Description: 演示sleep不释放lock(lock需要手动释放)
* @since unknown, 2020-04-15
*/
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "获取到了lock");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("线程" + Thread.currentThread().getName() + "释放了lock");
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}
//输出结果
线程Thread-0获取到了lock
线程Thread-0释放了lock(5s后)
线程Thread-1获取到了lock
线程Thread-1释放了lock(5s后)
- 和wait不同
3.3 sleep方法响应中断
- 抛出InterruptedException
- 清除中断状态
/**
* SleepInterrupted
*
* @author venlenter
* @Description: 每隔1s输出当前时间,被中断,观察
* Thread.sleep()
* TimeUnit.SECONDS.sleep()
* @since unknown, 2020-04-15
*/
public class SleepInterrupted implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中断了");
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SleepInterrupted());
thread.start();
Thread.sleep(6500);
thread.interrupt();
}
}
//输出结果
Wed Apr 15 23:09:55 CST 2020
Wed Apr 15 23:09:56 CST 2020
Wed Apr 15 23:09:57 CST 2020
Wed Apr 15 23:09:58 CST 2020
Wed Apr 15 23:09:59 CST 2020
Wed Apr 15 23:10:00 CST 2020
Wed Apr 15 23:10:01 CST 2020
我被中断了
java.lang.InterruptedException: sleep interrupted
Wed Apr 15 23:10:01 CST 2020
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at ConcurrenceFolder.mooc.threadConcurrencyCore.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:21)
at java.lang.Thread.run(Thread.java:748)
Wed Apr 15 23:10:02 CST 2020
Wed Apr 15 23:10:03 CST 2020
3.4 sleep总结
- sleep方法可以让线程进入Waiting状态,并且不占用CPU资源
- 但是不释放锁,直到规定时间后再执行
- 休眠期间如果被中断,会抛出异常并清除中断状态
3.5 sleep常见面试问题
wait/notify、sleep异同(方法属于哪个对象?线程状态怎么切换?)
(1)相同
- 都会阻塞
- 都可以响应中断
外层执行thread.interrupt()
try {
wait();
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
(2)不同
- wait/notify需要在synchronized方法中,而sleep不需要
- 释放锁:wait会释放锁,而sleep不释放锁
- 指定时间:sleep必须传参时间,而wait有多个构造方法,不传时间则直到自己被唤醒
- 所属类:wait/notify是Object方法,sleep是Thread类的方法
4、join方法
4.1 作用:因为新的线程加入了“我们”,所以“我们”要等他执行完再出发
4.2 用法:(在main方法中thread1.join)main等待thread1执行完毕,注意谁等谁(父等待子)
4.3 三个例子
- 普通用法
/**
* Join
*
* @author venlenter
* @Description: 演示join,注意语句输出顺序,会变化
* @since unknown, 2020-04-15
*/
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
});
thread1.start();
thread2.start();
System.out.println("开始等待子线程运行完毕");
thread1.join();
thread2.join();
System.out.println("所有子线程执行完毕");
}
}
//输出结果
开始等待子线程运行完毕
Thread-0执行完毕
Thread-1执行完毕
所有子线程执行完毕
- 遇到中断
/**
* JoinInterrupt
*
* @author venlenter
* @Description: 演示join期间被中断的效果
* @since unknown, 2020-04-21
*/
public class JoinInterrupt {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 finished.");
} catch (InterruptedException e) {
System.out.println("子线程中断");
}
}
});
thread1.start();
System.out.println("等待子线程运行完毕");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主线程中断了");
thread1.interrupt();
}
System.out.println("子线程已运行完毕");
}
}
//输出结果
等待子线程运行完毕
main主线程中断了
子线程已运行完毕
子线程中断
- 在join期间,线程到底是什么状态?:Waiting
/**
* JoinThreadState
*
* @author venlenter
* @Description: 先join再mainThread.getState()
* 通过debugger看线程join前后状态的对比
* @since unknown, 2020-04-22
*/
public class JoinThreadState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println(mainThread.getState());
System.out.println("Thread-0运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("等待子线程运行完毕");
thread.join();
System.out.println("子线程运行完毕");
}
}
//输出结果
等待子线程运行完毕
WAITING
Thread-0运行结束
子线程运行完毕
4.4 可以使用封装工具类:CountDownLatch或CyclicBarrier
4.5 join原理
- 源码
(1)thread.join();
(2)
public final void join() throws InterruptedException {
join(0);
}
(3)
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
- 分析:线程在run执行完成后,JVM底层会自动调用一个notifyAll唤醒,所以即使在join()内没有notify显示调用,执行完run()后,也会唤醒
- 等价
// thread.join(); 等价于下面synchronized的代码
synchronized (thread) {
thread.wait();
}
4.6 常见面试问题
- 在join期间,线程处于哪种线程状态?Waiting
5、yield方法
- 作用:释放我的CPU时间片。线程状态仍然是RUNNABLE,不释放锁,也不阻塞
- 定位:JVM不保证遵循yield逻辑
- yield和sleep区别:yield随时可能再次被调度
6、获取当前执行线程的引用:Thread.currentThread()方法
- 同一个方法,不同线程会打印出各自线程的名称
/**
* CurrentThread
*
* @author venlenter
* @Description: 演示打印majn, Thread-0, Thread-1
* @since unknown, 2020-04-22
*/
public class CurrentThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
new CurrentThread().run();
new Thread(new CurrentThread()).start();
new Thread(new CurrentThread()).start();
}
}
//输出
main
Thread-0
Thread-1
笔记来源:慕课网悟空老师视频《Java并发核心知识体系精讲》