javaSE学习笔记22-线程(thread)-线程通信、线程池

news/2025/2/22 23:40:24

线程通信


应用场景:生产者和消费者问题
    假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
    如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
    如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

线程通信-分析
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
    对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
    对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
    在生产者消费者问题中,仅有synchronized是不够的
        synchronized可阻止并发更新同一个共享资源,实现了同步
        synchronized不能用来实现不同线程之间的消息传递(通信)
    java提供了几个方法解决线程之间的通信问题(均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException)
    方法名                作用
    wait()               表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
    wait(long timeout)   指定等待的毫秒数
    notify()             唤醒一个处于等待状态的线程
    notifyAll()         唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

解决方式1
    并发协作模型”生产者/消费者模式“ -->管程法
        生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
        消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
        缓冲区:消费者不能直接使用生产者的数据,他们之间有个”缓冲区“
        生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

解决方式2
    并发协作模型”生产者/消费者模式“ -->信号灯法

方式1代码

java">package com.kuang.thread;

/*
线程通信
应用场景:生产者和消费者问题
    假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
    如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
    如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

线程通信-分析
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
    对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
    对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
    在生产者消费者问题中,仅有synchronized是不够的
        synchronized可阻止并发更新同一个共享资源,实现了同步
        synchronized不能用来实现不同线程之间的消息传递(通信)
    java提供了几个方法解决线程之间的通信问题(均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException)
    方法名                作用
    wait()               表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
    wait(long timeout)   指定等待的毫秒数
    notify()             唤醒一个处于等待状态的线程
    notifyAll()          唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

解决方式1
    并发协作模型”生产者/消费者模式“ -->管程法
        生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
        消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
        缓冲区:消费者不能直接使用生产者的数据,他们之间有个”缓冲区“
        生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

解决方式2
    并发协作模型”生产者/消费者模式“ -->信号灯法
 */

//测试:生产者消费者模型--》利用缓冲区解决:管程法

//生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        Synchronizer container = new Synchronizer();//创建缓冲区

        //创建生产者线程
        Producers producer = new Producers(container);
        //创建消费者线程
        Consumer consumer = new Consumer(container);

        //启动生产者和消费者线程
        producer.start();
        consumer.start();
    }
}

//生产者
class Producers extends Thread{
    Synchronizer container;//定义一个容器

    public Producers(Synchronizer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 1; i < 20; i++) {
            System.out.println("生产了第 " + i + " 个产品");
            container.push(new Product(i));//将产品放入缓冲区
            try{
                Thread.sleep(1000);//模拟生产耗时
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer extends Thread{
    Synchronizer container;//定义一个容器

    public Consumer(Synchronizer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 1; i < 20; i++) {
            Product product = container.pop();//从缓冲区(仓库)取出产品
            System.out.println("消费了第 " + product.id + " 个产品");

            try{
                Thread.sleep(3000);//模拟消费耗时
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

//产品
class Product {

    int id;//产品编号

    public Product(int id){
        this.id = id;
    }
}

//缓冲区(商店/仓库)
class Synchronizer {

    //需要一个容器大小
    Product[] products = new Product[10];
    //容器计数器
    int count = 0;

    //生产者放入产品
    public synchronized void push(Product product) {
        //如果容器满了,就需要等待消费者消费,生产者等待
        if(count == products.length) {
            System.out.println("缓冲区(仓库)已满,生产者等待...");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没有满,我们就需要丢入产品(放入产品)
        products[count] = product;
        count++;
        System.out.println("生产者生产放入第 " + product.id + " 个产品,当前缓冲区里有了:" + count +"个产品");

        this.notifyAll();//通知消费者可以消费了
    }

    //消费者消费产品
    public synchronized Product pop(){
        //如果容器为空,消费者等待
        while(count == 0){
            try {
                System.out.println("缓冲区为空,消费者等待...");
                wait();//消费者等待
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        //如果容器不为空,取出产品
        count--;
        Product product = products[count];
        System.out.println("消费者消费取出了第 " + product.id + " 个产品,当前缓冲区里还剩:" + count + "个产品");

        //如果缓冲区(仓库)为空,没有商品了,立即通知生产者生产商品
        if (count == 0) {
            System.out.println("缓冲区为空,仓库没有商品了,通知生产者生产商品...");
            this.notifyAll();//通知生产者可以生产了
        }
        return  product;
    }
}

这段代码实现了一个经典的生产者-消费者模型,通过线程间的通信和同步机制(wait() 和 notify())来协调生产者和消费者的行为。以下是代码的详细分析和实现逻辑:


代码结构

  1. TestPC 类

    • 主类,包含 main 方法,用于启动生产者和消费者线程。

    • 创建了一个缓冲区(Synchronizer 对象),并启动生产者和消费者线程。

  2. Producers 类

    • 生产者线程,负责生产产品并将其放入缓冲区。

    • 通过 container.push() 方法将产品放入缓冲区。

  3. Consumer 类

    • 消费者线程,负责从缓冲区取出产品并消费。

    • 通过 container.pop() 方法从缓冲区取出产品。

  4. Product 类

    • 表示产品的类,包含一个 id 属性,用于标识产品的编号。

  5. Synchronizer 类

    • 缓冲区类,用于存储产品。

    • 提供了 push() 和 pop() 方法,分别用于生产者和消费者操作缓冲区。

    • 使用 wait() 和 notify() 实现线程间的通信和同步。


代码逻辑

1. 生产者逻辑
  • 生产者线程通过 run() 方法不断生产产品。

  • 每次生产一个产品后,调用 container.push() 方法将产品放入缓冲区。

  • 如果缓冲区已满,生产者会调用 wait() 进入等待状态,直到消费者消费产品后唤醒它。

  • 生产者生产完产品后,会调用 notifyAll() 通知消费者可以消费了。

2. 消费者逻辑
  • 消费者线程通过 run() 方法不断从缓冲区取出产品并消费。

  • 每次消费一个产品时,调用 container.pop() 方法从缓冲区取出产品。

  • 如果缓冲区为空,消费者会调用 wait() 进入等待状态,直到生产者生产产品后唤醒它。

  • 消费者消费完产品后,如果发现缓冲区为空,会调用 notifyAll() 通知生产者可以生产了。

3. 缓冲区逻辑
  • 缓冲区是一个固定大小的数组(products),用于存储产品。

  • count 表示当前缓冲区中的产品数量。

  • push() 方法用于生产者将产品放入缓冲区。如果缓冲区已满,生产者会等待。

  • pop() 方法用于消费者从缓冲区取出产品。如果缓冲区为空,消费者会等待。

  • 使用 wait() 和notifyAll()  实现线程间的通信和同步。


代码执行流程

  1. 生产者生产产品

    • 生产者生产产品并调用 push() 方法将产品放入缓冲区。

    • 如果缓冲区已满,生产者会进入等待状态。

  2. 消费者消费产品

    • 消费者调用 pop() 方法从缓冲区取出产品。

    • 如果缓冲区为空,消费者会进入等待状态。

  3. 线程通信

    • 当生产者放入产品后,会调用 notifyAll()  唤醒等待的消费者。

    • 当消费者取出产品后,如果发现缓冲区为空,会调用 notifyAll()  唤醒等待的生产者。

关键点

  1. 线程同步

    • 使用 synchronized 关键字确保对缓冲区的操作是线程安全的。

    • 生产者和消费者通过 wait() 和notifyAll()  实现线程间的通信。

  2. 缓冲区的作用

    • 缓冲区作为共享资源,协调了生产者和消费者的操作。

    • 生产者将产品放入缓冲区,消费者从缓冲区取出产品。

  3. 线程通信

    • 当缓冲区满时,生产者会等待,直到消费者消费产品后唤醒它。

    • 当缓冲区为空时,消费者会等待,直到生产者生产产品后唤醒它。

  4. 改进点

    • 在消费者消费完产品后,如果发现缓冲区为空,会立即通知生产者生产商品,确保缓冲区不会长时间处于空的状态。

方式2代码 

java">package com.kuang.thread;


//测试生产者消费者问题2:信号灯法,标志位解决
//并发协作模型“生产者/消费者模式” --》信号灯法
public class TestPC2 {

    public static void main(String[] args) {
        TV tv = new TV();

        new Player(tv).start();
        new Watcher(tv).start();
    }
}

//生产者--》演员
class Player extends Thread{
    TV tv;
    public Player(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0){
                this.tv.play("快乐大本营");
            }else {
                this.tv.play("抖音-记录美好生活");
            }
        }
    }
}


//消费者--》观众
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

//产品--》节目
class TV{
    //演员表演,观众等待
    //观众观看,演员等待
    String voice;//表演的节目
    boolean flag = true;//定义标志词flag

    //表演
    public synchronized void play(String voice){

        if (!flag){//如果flag为假
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("演员表演了:" + voice);
        //通知观众观看
        this.notifyAll();//通知唤醒
        this.voice = voice;
        this.flag = !this.flag;
    }

    //观看
    public synchronized void watch(){

        if (flag) {
            try{
                this.wait();
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了:" + voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

这段代码实现了一个简单的生产者-消费者模型,使用了“信号灯法”(也称为标志位法)来协调生产者和消费者之间的同步。代码的核心思想是通过一个共享的标志位(flag)来控制生产者和消费者的执行顺序,确保生产者和消费者交替执行。

代码结构分析

  1. TestPC2 类

    • 这是程序的入口类,包含 main 方法。

    • 在 main 方法中,创建了一个 TV 对象(共享资源),并启动了一个生产者线程(Player)和一个消费者线程(Watcher)。

  2. Player 类

    • 这是生产者线程类,继承自 Thread

    • 在 run 方法中,生产者会循环 20 次,交替生产两种节目:“快乐大本营”和“抖音-记录美好生活”。

    • 生产者通过调用 TV 对象的 play 方法来生产节目。

  3. Watcher 类

    • 这是消费者线程类,继承自 Thread

    • 在 run 方法中,消费者会循环 20 次,调用 TV 对象的 watch 方法来消费节目。

  4. TV 类

    • 这是共享资源类,包含了生产者和消费者共享的数据和方法。

    • voice 字段表示当前播放的节目。

    • flag 字段是一个标志位,用于控制生产者和消费者的执行顺序。

    • play 方法和 watch 方法都是同步方法(synchronized),确保同一时间只有一个线程可以访问这些方法。

代码执行流程

  1. 生产者(Player)执行流程

    • 生产者调用 play 方法时,首先检查 flag 是否为 false。如果 flag 为 false,表示消费者正在消费,生产者需要等待(调用 wait 方法)。

    • 如果 flag 为 true,生产者可以执行生产操作,输出节目名称,并调用 notifyAll 方法唤醒等待的消费者线程。

    • 生产完成后,生产者将 flag 设置为 false,表示消费者可以开始消费。

  2. 消费者(Watcher)执行流程

    • 消费者调用 watch 方法时,首先检查 flag 是否为 true。如果 flag 为 true,表示生产者正在生产,消费者需要等待(调用 wait 方法)。

    • 如果 flag 为 false,消费者可以执行消费操作,输出当前播放的节目名称,并调用 notifyAll 方法唤醒等待的生产者线程。

    • 消费完成后,消费者将 flag 设置为 true,表示生产者可以开始生产。

关键点

  • synchronized 关键字:确保 play 和 watch 方法是线程安全的,同一时间只有一个线程可以执行这些方法。

  • wait 和 notifyAll 方法:用于线程间的通信和同步。wait 方法使当前线程进入等待状态,直到其他线程调用 notifyAll 方法唤醒它。

  • flag 标志位:用于控制生产者和消费者的执行顺序。flag 为 true 时,生产者可以生产;flag 为 false 时,消费者可以消费。

总结

这段代码通过使用 synchronizedwait 和 notifyAll 方法,以及一个标志位 flag,实现了一个简单的生产者-消费者模型。生产者和消费者交替执行,确保生产者生产一个节目后,消费者才能消费,反之亦然。这种模式可以有效地解决多线程环境下的同步问题。

线程池

使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:
    提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理(...)
        corePoolSize:核心池的大小
        maximumPoolSize:最大线程数
        keepAliveTime:线程没有任务时最多保持多长时间后会终止

JDK 5.0起提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
    <T>Future<T>submit(Callable<T>task):执行任务,有返回值,一般又来执行Callable
    void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

练习代码:

java">package com.kuang.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
线程池
使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:
    提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理(...)
        corePoolSize:核心池的大小
        maximumPoolSize:最大线程数
        keepAliveTime:线程没有任务时最多保持多长时间后会终止

JDK 5.0起提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
    <T>Future<T>submit(Callable<T>task):执行任务,有返回值,一般又来执行Callable
    void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
 */
//测试线程池
public class TestPool {

    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool 参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);//创建线程池

        //执行线程
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭连接
        service.shutdownNow();
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {

            System.out.println(Thread.currentThread().getName());

    }
}

这段代码演示了如何使用 Java 的线程池(ExecutorService)来管理和执行多线程任务。线程池是一种用于管理多个线程的机制,它可以避免频繁创建和销毁线程,从而提高程序的性能和资源利用率。

代码结构分析

  1. TestPool 类

    • 这是程序的入口类,包含 main 方法。

    • 在 main 方法中,创建了一个固定大小的线程池,并使用线程池执行多个任务。

  2. MyThread 类

    • 这是一个实现了 Runnable 接口的类,表示一个可以被线程执行的任务。

    • 在 run 方法中,任务简单地输出当前线程的名称。

代码执行流程

  1. 创建线程池

    • 使用 Executors.newFixedThreadPool(10) 创建一个固定大小为 10 的线程池。这意味着线程池中最多可以同时运行 10 个线程。

    • ExecutorService 是线程池的接口,Executors 是一个工具类,提供了创建不同类型线程池的静态方法。

  2. 提交任务

    • 使用 service.execute(new MyThread()) 方法向线程池提交任务。execute 方法接受一个 Runnable 对象,并将其放入线程池中等待执行。

    • 代码中提交了 4 个 MyThread 任务。

  3. 任务执行

    • 线程池中的线程会从任务队列中取出任务并执行。每个任务的 run 方法会被调用,输出当前线程的名称。

    • 由于线程池的大小是 10,而任务只有 4 个,因此这些任务会被线程池中的线程立即执行。

  4. 关闭线程池

    • 使用 service.shutdownNow() 方法关闭线程池。shutdownNow 会尝试停止所有正在执行的任务,并返回等待执行的任务列表。

    • 关闭线程池后,不能再向线程池提交新的任务。

关键点

  • 线程池的优势

    • 提高响应速度:线程池中的线程是预先创建好的,任务提交后可以直接执行,减少了创建线程的时间。

    • 降低资源消耗:线程池中的线程可以重复利用,避免了频繁创建和销毁线程的开销。

    • 便于线程管理:线程池提供了对线程的统一管理,可以控制线程的数量、生命周期等。

  • ExecutorService 接口

    • execute(Runnable command):执行一个没有返回值的任务。

    • submit(Callable<T> task):执行一个有返回值的任务,返回一个 Future 对象。

    • shutdown():平缓关闭线程池,等待所有任务执行完毕。

    • shutdownNow():立即关闭线程池,尝试停止所有正在执行的任务。

  • Executors 工具类

    • 提供了创建不同类型线程池的静态方法,如 newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutor 等。

总结

这段代码展示了如何使用 Java 的线程池来管理和执行多线程任务。通过使用线程池,可以有效地管理线程资源,避免频繁创建和销毁线程,从而提高程序的性能和资源利用率。线程池是多线程编程中非常重要的工具,特别适用于需要处理大量短期任务的场景。


http://www.niftyadmin.cn/n/5861299.html

相关文章

现代游戏UI架构深度解析——以UIController为核心的模块化界面管理系统

一、架构全景与设计哲学 本文将以重构后的UIController为核心&#xff0c;深入探讨Unity引擎下的高效UI管理方案。该体系采用"分层-分治"设计理念&#xff0c;通过界面生命周期管理、动态适配策略、资源优化机制三个维度的协同工作&#xff0c;构建了适应复杂交互需…

Transformer Decoder 详解

Transformer Decoder 详解 1. 模型结构图解 Transformer Decoder 由 N 个相同的层堆叠而成,每个层包含以下组件(与 Encoder 不同,Decoder 包含两种注意力机制): Input (Shifted Output) │ ├─> Masked Multi-Head Self-Attention → Add & LayerNorm → Encod…

23种设计模式 - 组合模式

模式定义 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;通过将对象组织成树形结构&#xff0c;使客户端能够以统一的方式处理单个对象和组合对象。该模式特别适用于需要表示“部分-整体”层次结构的场景&#xff0c;例如数控系统中的多…

Python装饰器根本写法

将上述流程进行了包装&#xff0c;就是装饰器 def outer(origin):def inner():# add coderes origin()# add codereturn resreturn innerouter def func():# code hereprint("func")return被包装的函数的没有参数&#xff0c;可以轻松完成代码&#xff0c;如果有参…

SeaTunnel社区「Demo方舟计划」首期活动上线—— MySQL CDC实时同步至PostgreSQL实战

引言 凌晨2点&#xff0c;某电商公司的数据工程师小李正对着屏幕抓狂——业务部门临时要求将MySQL的订单表实时同步到PostgreSQL进行分析&#xff0c;众所周知&#xff0c;在数据驱动的业务场景中&#xff0c;异构数据源同步是高频刚需。 以MySQL到PostgreSQL的CDC同步为例&a…

Kubernetes的Ingress 资源是什么?

在Kubernetes中&#xff0c;Ingress资源是一种用于管理集群外部对内部服务访问的API对象&#xff0c;主要用于将不同的外部请求路由到集群内的不同服务&#xff0c;以下是关于它的详细介绍&#xff1a; 定义与作用 Ingress资源定义了从集群外部到内部服务的HTTP和HTTPS路由规…

基于STM32与IFX007T的电机驱动全解析(无人机/机器人实战)

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 一、硬件系统架构1.1 核心部件简介 二、核心原理详解2.1 PWM调速技术2.2 H桥驱动原理 三、硬件连接详解3.1 典型接线图…

Three.js 快速入门教程【一】开启你的 3D Web 开发之旅

系列文章目录 Three.js 快速入门教程【一】开启你的 3D Web 开发之旅 Three.js 快速入门教程【二】透视投影相机 Three.js 快速入门教程【三】渲染器 Three.js 快速入门教程【四】三维坐标系 Three.js 快速入门教程【五】动画渲染循环 Three.js 快速入门教程【六】相机控件 Or…