- 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?
- 用3种方式实现生产者模式
- Join和sleep和wait期间线程的状态分别是什么?为什么?
1. wait,notify,notifyAll方法
作用、用法:阻塞阶段、唤醒阶段、遇到中断
1.1阻塞阶段
- 另一个线程调用该对象的notify()方法且刚好被唤醒的是本线程。
- 另一个线程调用该对象的notifyAll()方法
- 过来wait(long timeout)规定的超时时间,如果传入0就是永久等待。
- 线程自身调用了interrupt()。
wait,notify,notifyAll作用、方法
- 阻塞阶段
- 唤醒阶段
- 遇到中断
wait、notify
/**
* 描述: 展示wait和notify的基本用法 1. 研究代码执行顺序 2. 证明wait释放锁
*/
public class Wait {
public static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
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()");
}
}
}
}
Thread-0开始执行了
线程Thread-1调用了notify()
线程Thread-0获取到了锁。
notifyAll
/**
* 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
*/
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
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 notified.");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
try {
System.out.println(Thread.currentThread().getName()+" waits to start.");
resourceA.wait();
System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
ThreadC notified.
Thread-1's waiting to end.
Thread-0's waiting to end.
如果将resourceA.notifyAll() 改为resourceA.notify() 输出结果
Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
ThreadC notified.
Thread-0's waiting to end.
wait只释放当前的那把锁
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.
2.生产者消费者设计模式
产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
为什么要使用生产者和消费者模式
- 解耦:
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。 - 支持并发:
生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。
使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。其实当初这个模式,主要就是用来处理并发问题的。 - 支持忙闲不均:
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
环形缓冲区(减少了内存分配的开销)
双缓冲区(减少了同步/互斥的开销)
/**
* 描述: 用wait/notify来实现生产者消费者模式
*/
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 final 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 final 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 final int maxSize;
private final 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个产品。
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下9
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下8
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下7
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下6
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下5
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下4
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下3
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下2
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下1
拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下0
仓库里有了1个产品。
仓库里有了2个产品。
...省略
为什么wait()方法要放在同步块中
防止出现Lost Wake-Up Problem (防止一直wait中)
https://blog.csdn.net/zl1zl2zl3/article/details/89236983
https://www.cnblogs.com/xiohao/p/7118102.html
为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里
- 这些方法存在于同步中;
- 使用这些方法必须标识同步所属的锁;
- 锁可以是任意对象,所以任意对象调用方法一定定义在Object类中。
- 一个线程可以加多把锁
3. sleep方法不释放锁
- 包括synchronized和lock
- 和wait不同
/**
* 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
*/
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-1获取到了monitor。
线程Thread-1退出了同步代码块
线程Thread-0获取到了monitor。
线程Thread-0退出了同步代码块
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).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-0获取到了锁
线程Thread-0已经苏醒
线程Thread-1获取到了锁
线程Thread-1已经苏醒
3.1 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(6500);
thread.interrupt();
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
TimeUnit.HOURS.sleep(3);
TimeUnit.MINUTES.sleep(25);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中断了!");
e.printStackTrace();
}
}
}
}
Wed Dec 23 16:24:15 CST 2020
我被中断了!
Wed Dec 23 16:24:22 CST 2020
java.lang.InterruptedException: sleep interrupted
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 com.kpioneer.thread.threadcoreknowledge.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:24)
at java.lang.Thread.run(Thread.java:748)
总结:sleep方法可以让线程进入到Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。
wait/notify 和 sleep 方法的异同
相同点:
- 它们都可以让线程阻塞。
- 它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。
不同点:
- wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
- 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。
- sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。
- wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。
4. join方法
4.1 join方法作用、用法
作用:因为新的线程加入了我们,所以我们需要等他执行完再出发
用法:main等待thread1执行完毕,注意谁等谁
4.2 join普通用法
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread = 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() + "执行完毕");
}
});
thread.start();
thread2.start();
System.out.println("开始等待子线程运行完毕");
thread.join();
thread2.join();
System.out.println("所有子线程执行完毕");
}
}
开始等待子线程运行完毕
Thread-1执行完毕
Thread-0执行完毕
所有子线程执行完毕
4.3 join遇到中断
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() + "主线程中断了");
}
System.out.println("子线程已运行完毕");
}
}
等待子线程运行完毕
main主线程中断了
子线程已运行完毕
Thread1 finished.
注意: 发现 子线程已运行完毕又打印了Thread1 finished. 说明子线程 并没有运行完毕
优化:在异常中指定子线程中断thread1.interrupt()
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主线程中断了");
thread1.interrupt();
}
树池
等待子线程运行完毕
main主线程中断了
子线程已运行完毕
子线程中断
Thread1 finished. 没有打印符合结果
4.4 在join期间,线程到底是什么状态
/**
* 描述: 先join再mainThread.getState()
* 通过debugger看线程join前后状态的对比
*/
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.5 join等价实现
/**
* 描述: 通过讲解join原理,分析出join的代替写法
*/
public class JoinPrinciple {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
});
thread.start();
System.out.println("开始等待子线程运行完毕");
// thread.join();
synchronized (thread) {
thread.wait();
}
System.out.println("所有子线程执行完毕");
}
}
开始等待子线程运行完毕
Thread-0执行完毕
所有子线程执行完毕
5 yield方法详解
作用:释放我的CPU时间片
定位:JVM不保证遵循
yield 线程可能再次被调度
网友评论