wait() 必须在 synchronized 同步块中使用,以确保线程安全。它使当前线程阻塞并释放锁,直到其他线程调用 notify() 或 notifyAll() 唤醒。为防止唤醒过早,需配合标记变量(如 w)判断条件是否满足。多个 wait() 线程可被 notify() 唤醒,但建议使用 notifyAll() 避免死锁。阻塞队列、线程池、定时器等均基于生产者-消费者模型实现,提升并发效率与资源复用。
wait notity
wait 等待阻塞
wait 必须加锁才能使用
解释
synchronized(锁对象){
//入锁
锁对象.wait();
//阻塞 等待唤醒
}
//出锁
例子
public static Object q=new Object();
public static Boolean w=false;
public static void main(String\[\] args) {
Thread a=new Thread(()->{
System.out.println("a线程wait前");
while (!w) {
synchronized (q) {
try {
q.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
System.out.println("a线程wait后");
});
a.start();
synchronized (q){
w=true;
q.notify();
System.out.println("a唤醒成功");
}
为什么要有标记
因为担心主线程唤醒的时候 子线程还没有执行wait代码 直接唤醒没有任何效果 notity叫醒了 标记不执行wait()的阻塞 跳过while 执行下面的代码
多个线程多个wait 一个唤醒notify
public static void main(String\[\] args) throws InterruptedException {
Thread a=new Thread(()->{
synchronized (q) {
while (!w) {
try {
System.out.println("1");
q.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
System.out.println("3");
});
Thread a1=new Thread(()->{
synchronized (q) {
while (!w) {
try {
System.out.println("2");
q.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
System.out.println("4");
});
a.start();
a1.start();
Thread.sleep(10000);
//等待两个线程都进入wait()阻塞
synchronized (q){
w=true;
q.notify();
System.out.println("唤醒");
}
}
sleep和wait区别
sleep和wait两者都具有沉睡功能
两则都可提前唤醒 sleep被Interrupt提前唤醒 实际上是改变标记点
wait必须上锁和解锁
sleep则不用
如果在synchronized锁里 wait会解锁 sleep则不会
Allnotify
Allnotify唤醒所有线程
Allnotify唤醒在被wait()沉睡的所有线程
模式
单例模式
单例模式在某个类 某个程序中 只有一个实例 new
单例模式分为饿汉模式和懒汉模式
饿汉模式
先创建好对象 通过方法获取对象名和对象
//饿汉模式 在类加载的时候就创建好了
public static Thread3 a=new Thread3();
private Thread3(){
System.out.println("饿汉模式");
}
//获取到创建好的对象
public static Thread3 a1(){
return a;
}
//拿到对象的名字
public void test(){
System.out.println(Thread.currentThread().getName()+" 拿到实例 "+this);
}
public static void main(String[] args) {
//执行任务
for (int i = 0; i < 5; i++) {
int id=i;
new Thread(()->{
Thread3 a=Thread3.a1();
a.test();
},"线程"+id).start();
}
}

懒汉模式
在第一次使用时才创建好对象
//懒汉模式在第一个使用时创建 而不是类加载的时候
private static Thread3 a=null;
public static Thread3 a1() {
//如果没有创建则创建 创建好了则返回
if (a == null) {
return new Thread3();
}
return a;
}
private Thread3(){
System.out.println("懒汉模式");
}
public void test() {
System.out.println(Thread.currentThread().getName() + " 拿到实例 " + this);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
Thread3 a3=Thread3.a1();
a3.test();
}).start();
}
}

懒汉模式的弊端

这个方法里线程是不安全的 线程安全要求操作是原子性的 唯一的
其中if和创建对象是两个 如果判断一下正要new 就切换其他线程 也要new

这个也解释了我们new了一次 调用一次构造方法 为什么会调用超过一次以上的构造方法
就破坏了单例模式 所以这两个操作就要绑定一起
解决方法
进行加锁操作
public static synchronized 类对象进行加锁 Thread3 a1() {
//如果没有创建则创建 创建好了则返回
synchronized (a4) {
if (a == null) {
a = new Thread3();
}
}
return a;
}

这种方法解决了线程的安全性 但是性能大大降低
一个线程在执行该方法时不论有没有创建对象 都会先阻塞 等拿到锁的线程先执行完
所以我们在没进锁之前先判断是否有没有对象 如果有直接返回 没有则加锁
if (a == null) {
//是否有没有实例
synchronized (a4) {
if (a == null) {
//进一步判断有没有实例
a = new Thread3();
}
}
return a;
}
a线程进入没有创建实例 拿到了锁 正要创建 此时b线程通过第一层在外面等待 a此时创建好了 b拿到了锁但是此时的a创建好了 则直接返回
这时要考虑内存的可见性问题
线程要读到主内存 实时更新 进行读操作 避免重排序问题 a加volatile
new Thread3() 3个指令
1分配内存
2初始化构造方法
3把内存首地址赋值给引用变量
//懒汉模式在第一个使用时创建 而不是类加载的时候
public static Object a4=new Object();
private static volatile Thread3 a=null;
public static Thread3 a1() {
//如果没有创建则创建 创建好了则返回
if (a == null) {
synchronized (a4) {
if (a == null) {
a = new Thread3();
}
}
}
return a;
}
private Thread3(){
System.out.println("懒汉模式");
}
public void test() {
System.out.println(Thread.currentThread().getName() + " 拿到实例 " + this);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
Thread3 a3=Thread3.a1();
a3.test();
}).start();
}
}
阻塞队列由来
服务器之间通过直接传输数据 请求和保持 大家都知道有高峰和低峰的时候 难免造成服务器的中断 这时候出现 一个间接工具来 间接传输数据 阻塞队列
把阻塞队列包装成服务器程序 单独使用机器来部署叫消息队列
消费生产模型工作原理
消费者 当队列满了开始阻塞 消费者去执行
生产者 当队列空了开始阻塞 生产者去生产
消费生产者模型的模拟实现
package Book3;
import java.util.concurrent.BlockingDeque;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28931
* Date: 2026-10-18
* Time: 13:59
*/
public class Tread4 {
//模拟实现阻塞队列
public static int[] a;
//个数
public static int size;
//入队列计数
public static int head;
//出队列计数
public static int w;
//初始化
public Tread4(int b){
a=new int[b];
}
//入队列
private synchronized void put(int a1) throws InterruptedException {
//while循环是因为多线程 如果一次下一个线程就判断不了
while (size>=a.length){
System.out.println("队列满了 开始阻塞 出队列");
//等完成了一个载入 又会回到这里
this.wait();
}
a[head]=a1;
head++;
size++;
//如果满了 满了就会清=0 消费者就会执行
if(head>=a.length){
head=0;
}
this.notifyAll();
}
//出队列
public synchronized int peek() throws InterruptedException {
while (size==0){
System.out.println("队列空 开始阻塞 入队列");
this.wait();
}
int q=a[w];
w++;
//如果获取的数已经超了就会重新回到0
if(w>=a.length){
w=0;
}
size--;
this.notifyAll();
return q;
}
//获取个数
public int getSize(){
return size;
}
public static void main(String[] args) {
Tread4 E=new Tread4(3);
//生产者
Thread producer = new Thread(() -> {
int i = 1;
while (true) {
try {
E.put(i);
System.out.println("✅生产:" + i + ",当前数量:" + E.getSize());
i++;
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//消费者
Thread consumer = new Thread(() -> {
int i=1;
while (true) {
try {
E.peek();
System.out.println("❌消费:" + i + ",当前数量:" + E.getSize());
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}

怎么调节notifyAll来唤醒wait
通过while条件 满足则进入 到wait()条件执行下面的条件
线程池
常量池
在java中有独立的内存空间 只需把内容加载到内存 实现创建和销毁
我们在使用和销毁进程太慢
线程消耗大怎么解决?
1线程池
2协程线程
为什么使用线程池比使用线程创建效率高?
线程是操作系统调用
操作系统分为内核和应用程序
线程是内核来调度 从应用程序切换到内核 不可控制过程
线程池是应用程序的 本身自带的jdk 线程比较安全
线程池
先创建好几个线程 线程创建好销毁 又返回线程池重新使用
使用线程池
Threadpoolexecutor
方法参数

int corepoolsize
核心线程 至少有几个线程
int maximumpoolsize
核心线程+非核心 最大线程
loog keepAliveTime
非核心线程最长存活时间
Timeunit unit
枚举
blockingqueue<Runnable>
工作队列
线程池本质也是消费生成模型
submit生产
线程消费
ThreadFactory
工厂方法
RejectedExecutionHandler handler
拒绝策略

1ThreadpoolExecutor.AbortPolicy
直接抛异常
2ThreadPoolExecutor.callerRunsPolicy
让调用submit的线程自行执行任务
submit(Runnable runnable)
{
if(满了&&callerRunsPolicy){
runnable.run();
return ;
}
queue.put(runnable);
return;
3ThreadPoolExecutor.DiscardoldestPolicy
丢弃最老的任务
4ThreadPoolExecutor.DiscardPolicy
丢弃当前的任务
工厂方法
把一个实例化的对象封装成方法 想用的时候直接调方法
模拟实现线程池
//阻塞队列
public BlockingDeque<Runnable> queue;
//线程数量
private final int a;
//线程标记
private volatile Boolean p=false;
//创建好线程
public Thread5(int a,int b) {
this.a = a;
queue=new LinkedBlockingDeque<>(b);
for (int i = 0; i < a; i++) {
Thread e=new Thread(()->{
try {
//接收任务 没有任务就阻塞 take()方法内有wait
Runnable w=queue.take();
//run()有notity去唤醒 运行labda表达式中的代码
w.run();
} catch (InterruptedException ex) {
//异常就终止循环
p=true;
System.out.println("线程中断"+Thread.currentThread().getThreadGroup());
}
}, "worker-" + i);;
e.start();
}
}
//调用运行主代码
public void sumbit(Runnable runnable) throws InterruptedException {
if(p){
throw new InterruptedException("线程池关闭 不能提交代码");
}
queue.put(runnable);
}
//关闭资源
public void set(){
p=true;
System.out.println("关闭资源");
}
public static void main(String\[\] args) throws InterruptedException {
// 构造:3个工作线程,队列最多放5个任务
Thread5 pool = new Thread5(3, 5);
// 提交10个测试任务
for (int i = 1; i <= 10; i++) {
int taskId = i;
pool.sumbit(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
Thread.sleep(1000);
System.out.println("准备关闭线程池");
pool.set();
}
模拟线程池怎么调用的
主函数调用自定义函数sumbit sumbit里调用que.put() 当线程里有队列就执行取出take函数 空了就阻塞 take()函数内有wiat等待 来一个执行一个 run函数里有notity唤醒 执行线程里的任务
返回值说明
0等于结束
130等于中途结束


定时器的使用
//定时器
Timer a=new Timer();
//定时器任务
TimerTask b=new TimerTask() {
@Override
public void run() {
System.out.println("1000");
}
};
//启动定时器
a.schedule(b,1000);
模拟实现定时器
public class Thread6 implements Comparable<Thread6> {
//任务
public Runnable runnable;
//绝对到期时间
public long time;
public Thread6(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
@Override
public int compareTo(Thread6 o) {
// 时间小(更早到期)排在堆顶
return Long.compare(this.time, o.time);
}
}
class a1 {
//管理延时队列:存放未到时间的任务
private PriorityBlockingQueue<Thread6> q = new PriorityBlockingQueue<>();
//工作执行队列:时间到的任务交给worker
private PriorityBlockingQueue<Thread6> q1 = new PriorityBlockingQueue<>();
public a1() {
Thread manager = new Thread(() -> {
while (true) {
try {
Thread6 task = q.take();
long now = System.currentTimeMillis();
if (task.time <= now) {
// ✅ 时间到,送入执行队列
q1.put(task);
} else {
// ✅ 时间未到,放回延时队列,等待剩余时长
q.put(task);
synchronized (q) {
q.wait(task.time - now);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}, "manager-thread");
manager.setDaemon(true);
manager.start();
Thread worker1 = new Thread(() -> {
while (true) {
try {
Thread6 task = q1.take();
// 真正执行业务代码,内层捕获任务异常,防止worker死掉
try {
task.runnable.run();
} catch (Exception ex) {
System.out.println("任务执行异常:");
ex.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}, "worker-thread");
worker1.setDaemon(true);
worker1.start();
}
//提交定时任务:delay 毫秒之后执行
public void schedule(Runnable runnable, long delay) {
long targetTime = System.currentTimeMillis() + delay;
q.put(new Thread6(runnable, targetTime));
}
public static void main(String[] args) {
a1 timer = new a1();
System.out.println("main提交任务:" + System.currentTimeMillis());
timer.schedule(() -> {
System.out.println("任务A执行,线程:" + Thread.currentThread().getName() + " 时间:" + System.currentTimeMillis());
}, 2000);
timer.schedule(() -> {
System.out.println("任务B执行,线程:" + Thread.currentThread().getName() + " 时间:" + System.currentTimeMillis());
}, 4000);
// 主线程等待,不然守护线程跟着直接退出看不到输出
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
调用思路
main方法-> a1对象的创建 把所需的线程先创建好 -> timer.schedule(() 创建好任务 交给管理ui列管理线程到了时间阻塞 当队列为空 也阻塞调用工作线程 队列空阻塞
比较方法由阻塞队列调用
另一种模拟"时间轮"
利用循环队列 每个元素是"时间单位" 每一个元素是链表 每到一个时间单位 遍历链表 对应的任务
性能高
不如优先级队列精度好