1 Thread 和 Object 类中的重要方法
1.1 wait
,notify
,notifyAll
作用、用法
- 作用、用法:阻塞阶段 、唤醒阶段、遇到中断
- 阻塞阶段
执行wait
方法的时候,必须先具有这个对象的monitor锁
直到遇到以下的4种情况,才会被唤醒:
1.1.1 普通用法
/**
* @Description wait和notify的基本用法
* 1、代码执行顺序
* 2、证明 wait释放锁
* @Author tzb
* @Date 2021/2/8 18:08
* @Version 1.0
**/
public class Wait {
public static Object obj = new Object();
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (obj) {
System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
// 释放锁
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+ Thread.currentThread().getName() + "获得锁");
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
synchronized (obj){
obj.notify();
System.out.println("线程" + Thread.currentThread().getName() + "调用notify");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
}
1.1.2 notify
和notifyAll
的用法
/**
* @Description 3个线程,线程1和线程2首先被阻塞,线程3唤醒他们。、
* 1、start先执行,不代表线程先启动
* @Author tzb
* @Date 2021/2/8 18:28
* @Version 1.0
**/
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " 获取resourceA lock");
try {
System.out.println(Thread.currentThread().getName() + " 等待开始");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + " 等待结束");
} 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();
System.out.println("ThreadC notified");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
}
- 修改代码
1.1.3 只释放当前monitor展示
- 执行
wait
这个方法的一定是一个对象,这个对象就代表那一把锁。哪一个对象执行wait
,就是释放掉哪一个对象的锁,而不影响其他锁的行为
/**
* @Description 证明wait只释放当前的那把锁
* @Author tzb
* @Date 2021/2/9 9:42
* @Version 1.0
**/
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 {
resourceA.wait();
System.out.println(" ThreadA 释放 resourceA lock");
} 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");
synchronized (resourceB) {
System.out.println(" ThreadB got resourceB lock");
}
}
}
});
thread1.start();
thread2.start();
}
}
1.2 手写生产者和消费者设计模式
/**
* @Description 用wait和notify实现
* @Author tzb
* @Date 2021/2/9 10:11
* @Version 1.0
**/
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 个产品
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 9
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 8
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 7
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 6
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 5
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 4
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 3
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 2
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 1
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 0
仓库里有1 个产品
仓库里有2 个产品
仓库里有3 个产品
仓库里有4 个产品
仓库里有5 个产品
仓库里有6 个产品
仓库里有7 个产品
仓库里有8 个产品
仓库里有9 个产品
仓库里有10 个产品
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 9
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 8
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 7
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 6
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 5
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 4
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 3
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 2
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 1
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 0
仓库里有1 个产品
仓库里有2 个产品
仓库里有3 个产品
仓库里有4 个产品
仓库里有5 个产品
仓库里有6 个产品
仓库里有7 个产品
仓库里有8 个产品
仓库里有9 个产品
仓库里有10 个产品
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 9
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 8
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 7
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 6
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 5
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 4
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 3
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 2
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 1
拿到了Tue Feb 09 14:11:28 CST 2021,仓库剩下 0
1.3 wait
,notify
,notifyAll
常见的面试问题
1.3.1 两个线程交替打印0-100奇偶数
- 方式1:
synchronized
,存在浪费的情况
/**
* @Description 两个线程交替打印0-100的奇偶数,
* synchronized实现
* @Author tzb
* @Date 2021/2/9 14:18
* @Version 1.0
**/
public class WaitNotifyPrintOddEvenSyn {
private static int count;
private static final Object lock = new Object();
// 新建2个线程,1个只处理偶数,另一个只处理奇数(位运算)
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);
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);
count++;
}
}
}
}
},"奇数").start();
}
}
- 方式2:
wait/notify
/**
* @Description 证明wait只释放当前的那把锁
* @Author tzb
* @Date 2021/2/9 9:42
* @Version 1.0
**/
public class WaitNotifyPrintOddEven {
private static int count = 0;
private static final 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();
}
}
1.3.2 手写生产者和消费者模式
1.3.3 wait
需要在同步代码块使用,而sleep
不需要
- 为了使得通信可靠,防止死锁或者永久的等待
1.3.4 为什么线程通信的方法 wait
,notify
,notifyAll
定义在Object
,sleep
定义在 Thread
wait
,notify
,notifyAll
是锁级别的操作,锁是属于某一个对象,不是在线程中,
1.3.5 wait
方法是属于Object对象的,调用 Thread.wait
会怎样?
- Thread也可以当做普通的锁,但是
Thread
类比较特殊,在线程退出的时候会自动执行notify
,会扰乱自己设计的流程。
1.3.6 notify
和notifyAll
的选取
1.3.7 notifyAll
之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么样?
- 回到最初始的状态
1.3.8 用 suspend
和resume
来阻塞线程可以吗?
- 不推荐,存在线程安全问题