【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法
【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法
- 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?
- 用3种方式实现生产者模式
- Join和sleep和wait期间线程的状态分别是什么?为什么?
一、方法概览
二、wait、notify、notifyAll方法详解
- 作用、用法:阻塞阶段、唤醒阶段、遇到中断
- 可以控制线程去休息或者唤醒
1、阻塞阶段
有时我们想让一个线程或者多个线程去休息一下,当我们需要它的时候,或者是时机成熟的时候,再去唤醒它,这就是上述三个方法的作用。一旦线程进入了休息的状态,就进入了阻塞阶段。在执行wait()方法的时候,需要先获得这个对象的moniter锁。
①知道以下四种情况之一发生时,才会被唤醒
- 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程
- 另一个线程调用这个对象的notifyAll()方法
- 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待
- 线程自身调用了interrupt()
2、唤醒阶段
-
notify()会唤醒单个正在等待某个对象moniter的线程,唤醒的时候如果有多个线程都在等待,它只会选取其中一个,具体选择哪一个是任意的。JVM可以有自己的实现
-
notify()和wait()必须在synchronized保护的代码块或者方法中去执行,如果在synchronized外部执行,会抛出异常。
-
notifyAll()会把所有等待的线程一次性唤醒,至于哪一个线程会获得释放的moniter锁,依赖于操作系统线程的调度。
3、遇到中断
假设一个线程已经执行了wait()方法,在此期间,如果被中断了,会抛出InterruptedException,并且释放掉已经获取到的moniter锁。
4、普通用法
①wait/notfy
测试代码
/**
* 描述: 展示wait和notify的基本用法
* 1.研究代码的执行顺序
* 2.证明wait释放锁
*/
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的moniter锁
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) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
②Wait/notifyAll
/**
* 描述: 3个线程,线程和线程2首先被阻塞,线程3唤醒它们。
* notify,notifyAll区别
* start先执行不代表线程先启动
*/
public class WaitNotifyAll implements Runnable{
private static final Object resourceA = new Object();
@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 toend.");
} 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("Thread C notified");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
}
③只释放当前moniter演示
/**
* 描述: 证明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();
}
}
5、wait、notify、notifyAll的特点、性质
- 用之前必须先拥有对象的monitor
- 只能唤醒其中一个
- 属于Object类
- 类似功能的Condition
- 同时持有多个锁的情况
- 可能会导致死锁的发生
①wait的原理
重新理解线程的六个状态,状态转化的特殊情况
三、手写生产者消费者设计模式
1、为什么要使用生产者和消费者模式
为了解决生产者消费者速度不匹配的问题,而提出的设计模式
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 EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
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();
}
}
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();
}
}
}
四、wait、notify常见面试问题
1、用两个线程交替的打印0~100的奇偶数
①Synchronized关键字实现
/**
* 描述:两个线程交替打印0~100的奇偶数,用synchronized关键字实现
*/
public class WaitNotifyPrintOddEvenSyn {
private static int count;
private static final Object lock = new Object();
//新建两个线程
//一个只处理偶数,一个只处理奇数,用位运算
//用synchronized来通信
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100) {
synchronized (lock) {
//取出最低位,如果是1,那么是奇数,如果是0那么是偶数
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) {
//取出最低位,如果是1,那么是奇数,如果是0那么是偶数
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
},"奇数线程").start();
}
}
- 这种方法比较浪费时间,相当于两个线程不断地去竞争这个锁。
②更好的方法:wait/notify
/**
* 描述: 两个线程交替打印0 ~ 100的奇偶数
* 用wait和notify提高效率
*/
public class WaitNotifyPrintOddEvenWait {
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) {
new Thread(new TurningRunner(), "偶数线程").start();
new Thread(new TurningRunner(), "奇数线程").start();
}
}
}
2、手写生产者消费者设计模式
3、为什么wait()需要在同步代码块内使用,而sleep()不需要
为了通信的可靠,防止死锁或者永久等待的发生。如果没有synchronized代码块保护,就可以在执行完wait之后,直接切换到另外一个线程,没想到对方已经执行完notify了,这样永远没有线程去唤醒,陷入永久等待,或者死锁的发生。而sleep()是针对于自己,单独线程的,不需要放到同步代码块当中。
4、为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?
主要是因为,wait(),notify(),notifyAll()是针对于锁级别的操作,而锁是针对于对象而言的。锁是绑定到某一个对象当中,而不是某一个线程当中。一个线程可以持有多把锁,如果把这些方法定义在Thread类里,就没有办法实现如此灵活的锁粒度逻辑了。
5、wait方法是属于Object对象的,那调用Thread.wait会怎么样?
这种方式是可以的,线程退出的时候会自动的执行notify(),我们在创建锁对象的时候不要使用Thread类。
6、如何选择用notify还是notifyAll?
目的是向唤醒一个还是多个线程?
7、notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?
陷入到等待的状态,等待持有者再次释放,再去抢。
8、用suspend()和resume()来阻塞线程可以吗?为什么?
由于安全问题,被弃用了,不推荐。推荐使用wait()和notify()来实现
五、sleep方法详解
1、作用
我只想让线程在预期的时间执行,其他时候不要占用CPU资源。
特点:不释放锁,包括ynchronized和lock
和wait不同
2、演示不释放synchronized
/**
* 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
*/
public class SleepDontReleaseMonitor implements Runnable{
@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() + "退出了同步代码块");
}
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
}
3、演示不释放lock锁
/**
* 描述:演示sleep不释放lock(lock需要手动释放)
*/
public class SleepDontReleaseLock implements Runnable{
private static final Lock lock = new ReentrantLock();
@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();
}
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}
4、sleep方法响应中断
1、抛出InterruptedException
2、清除中断状态
/**
* 描述: 每隔1秒钟输出当前时间,被中断,观察。
* Thread.sleep()
* TimeUnit.SECONDS.sleep()
*/
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();
}
}
5、一句话总结
sleep方法可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间之后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。
6、常见面试问题
①wait/notify、sleep异同(方法属于那个对象?线程状态怎么切换?)
- 相同
- 都会让线程进入阻塞状态
- 响应中断
- 不同
- wait/notify要在同步方法、同步代码块中去执行,防止死锁,或者永久等待
- 释放锁:wait()会释放锁,sleep()不会释放
- 指定时间:sleep必须传参
- 所属类:原因:Object类,锁是针对于对象而言的,而不是线程。可以满足一个线程多把锁。
六、join方法详解
1、join方法作用、用法
作用:因为新的线程加入了我们,所以我们要等他执行完再出发
用法:main等待thread1执行完毕,注意谁等谁
2、普通用法
/**
* 描述:演示join,注意语句输出顺序,会变化
*/
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("所有子线程执行完毕");
}
}
运行结果:
取消掉join注释:
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) {
e.printStackTrace();
}
}
});
thread1.start();
System.out.println("等待子线程运行完毕");
try {
//这里意思是主线程在等待子线程,但是被子线程打断
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断了");
e.printStackTrace();
}
System.out.println("子线程运行完毕");
}
}
运行结果
通过上述结果分析,主线程打印了子线程运行完毕,但其实子线程并没有运行完毕,这是因为子线程中断了主线程之后,进入休眠,过了5秒才执行下一句。
改进:
/**
* 描述: 演示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(Thread.currentThread().getName() + "被中断了");
}
}
});
thread1.start();
System.out.println("等待子线程运行完毕");
try {
//这里意思是主线程在等待子线程,但是被子线程打断
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断了");
thread1.interrupt();
}
System.out.println("子线程运行完毕");
}
}
运行结果:
相当于是把这个中断由主线程又传递给了子线程。
4、join期间线程是什么状态:Waiting
/**
* 描述; 先join再mainThread.getState();
* 通过debugger看各种线程的状态
*/
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("子线程运行完毕");
}
}
5、join源码分析
我们可以看到,在join的源码中使用了wait()方法,使得主线程进入等待,那么join()结束之后,并没有执行notify()方法,是谁让主函数唤醒的呢?这其实是JVM的一个实现,在Thread类执行完run函数之后,会自动的notify。这也是为什么,不把wait()方法放到Thread类里面了。
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();
//不用唤醒,因为子线程run方法执行完之后,自动唤醒
}
System.out.println("所有子线程执行完毕");
}
}
七、yield方法详解
作业:释放我的CPU时间片
定位:JVM不保证遵循
和sleep区别:是否随时可能再次被调度,yield可以立刻的被调度,而sleep不可以
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?