javaEE 多线程4

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列管理线程到了时间阻塞 当队列为空 也阻塞调用工作线程 队列空阻塞

比较方法由阻塞队列调用

另一种模拟"时间轮"

利用循环队列 每个元素是"时间单位" 每一个元素是链表 每到一个时间单位 遍历链表 对应的任务

性能高

不如优先级队列精度好

相关推荐
大陈子编程1 小时前
C++ 背包九讲:从入门到精通的动态规划专题
开发语言·c++·动态规划
qq_401700411 小时前
Qt 组播(Multicast)实战详解
开发语言·qt
Sapphire~1 小时前
odoo-095 视图的重写规则(全部重写还是部分改写)
开发语言·javascript·ecmascript
利威尔xu1 小时前
Go GMP 调度器 10 个高频追问:3 个线上故障让 P99 从 80ms 飙到 2s、10 万 goroutine 泄漏
开发语言·后端·面试·性能优化·golang
中电金信1 小时前
从业务调优到分层缓存:DeepSeek V4 Flash的PPU实践
java·开发语言
梦影_1 小时前
MySQL进阶教程(二)—— 储存过程与触发器(下)
开发语言·数据库·后端·sql·mysql·oracle·数据库开发
snow@li1 小时前
ruoyi-vue-pro/芋道源码:一键改包名/用项目自带的 ‌ProjectReactor‌ 程序/彻底、省事
java
lg_cool_1 小时前
Python 代码打包
开发语言·python
熊出没1 小时前
CommitLoom 使用教程:在 IntelliJ IDEA 中自动生成 Git 提交信息
java·git·intellij-idea