2.2 JAVA的锁--线程协作

57 阅读3分钟

一、生产者消费者问题

1.1 线程通信

image.png

应用场景:生产者和消费者问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走
  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库空
  • 如果仓库有产品,则消费者可以将其取走,否则停止消费,直到仓库满

1.2 分析

这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件

  • 对于生产者,没有生产产品之前,要通知消费者等待。而生产产品之后,要通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,要生产新的产品
  • 在这个问题中,仅有synchronized是不够的
    • synchronized可以阻止并发更新同一个资源,实现了同步
    • synchronized不能实现不同线程之间的消息传递(通信)

1.3 线程通信

Java提供了几个方法用于解决线程之间的通信问题:

image.png

注意:这些均为Object类的方法,都只能再同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

1.4 解决方式

(1)管程法

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿数据

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区
 /**
  * @ClassName ProductorAndComsumerTest
  * @Description 生产者和消费者--管程法
  * @Author wangwk-a
  * @Date 2022/1/2 19:10
  * @Version 1.0
  */
 public class ProductorAndComsumerTest {
     public static void main(String[] args) {
         SyncContainer container = new SyncContainer();
         new Productor(container).start();
         new Comsumer(container).start();
     }
 }
 ​
 /**
  * 生产者
  */
 class Productor extends Thread {
     public static final int CHICKEN_NUMS = 100;
     SyncContainer container;
 ​
     public Productor(SyncContainer container) {
         this.container = container;
     }
 ​
     /**
      * 生产
      */
     @Override
     public void run() {
         for (int i = 0; i < CHICKEN_NUMS; i++) {
             try {
                 container.push(new Chicken(i));
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("成产了" + i + "只鸡");
         }
     }
 }
 ​
 /**
  * 消费者
  */
 class Comsumer extends Thread {
     public static final int CHICKEN_NUMS = 100;
     SyncContainer container;
 ​
     public Comsumer(SyncContainer container) {
         this.container = container;
     }
 ​
     /**
      * 消费
      */
     @Override
     public void run() {
         for (int i = 0; i < CHICKEN_NUMS; i++) {
             try {
                 System.out.println("消费了" + container.pop().id + "只鸡");
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
 }
 ​
 /**
  * 缓冲器
  */
 class SyncContainer {
     /**
      * 大小
      */
     Chicken[] chickens = new Chicken[10];
 ​
     /**
      * 容器计数器
      */
     int count = 0;
 ​
     /**
      * 生产者放入产品
      */
     public synchronized void push(Chicken chicken) throws InterruptedException {
         if (count == chickens.length) {
             // 如果容器满了,就等待消费者消费,生产者等待
             this.wait();
         }
 ​
         // 如果没有满,就放入产品
         chickens[count] = chicken;
         count++;
         // 通知消费者消费
         this.notify();
     }
 ​
     /**
      * 消费者消费产品
      */
     public synchronized Chicken pop() throws InterruptedException {
         if (count == 0) {
             // 如果容器为空,就等待生产者生产
             this.wait();
         }
 ​
         // 如果有产品,消费
         count--;
         Chicken ch = chickens[count];
         // 通知生产者生产
         this.notify();
         return ch;
     }
 }
 ​
 /**
  * 产品
  */
 class Chicken {
     /**
      * 产品编号
      */
     int id;
 ​
     public Chicken(int id) {
         this.id = id;
     }
 }

(2)信号量

通过标志位来判断

二、线程池

2.1 介绍

背景:经常创建和销毁、使用量特别打的资源,比如并发情况下的线程,对性能影响很大

思路:提前创建好多个线程,放入线程池中,使用时直接获取,放用完放回池中。可以避免频繁创建和销毁、实现重复利用。

好处:

  • 提高响应速度(减少了创建新线程的时间)

  • 降低资源消耗(重复利用线程池中线程,不需要每次创建)

  • 便于线程管理

    • corePoolSize:核心池大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

2.2 使用线程池

  • JDK5.0提供线程池相关APIExecutorServiceExecutors
  • ExecutorService:真正的线程池接口,常见子类**ThreadPoolExecutor**

void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable

<T> Future<T> submit(Callable<T> task) :执行任务,有返回值,一般用来执行Callable

void shutdown() :关闭连接池

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

Runnable示例:

 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 ​
 /**
  * @ClassName ThreadPoolTEst
  * @Description 测试线程池
  * @Author wangwk-a
  * @Date 2022/1/2 19:43
  * @Version 1.0
  */
 public class ThreadPoolTest {
     public static void main(String[] args) {
         // 创建线程池
         ExecutorService service = Executors.newFixedThreadPool(10);
 ​
         // 执行
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
 ​
         // 关闭连接
         service.shutdown();
     }
 }
 ​
 class MyThread implements Runnable {
     public static final int THREAD_NUM = 10;
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName());
     }
 }
 pool-1-thread-1
 pool-1-thread-4
 pool-1-thread-2
 pool-1-thread-3

Callable示例:

 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 ​
 /**
  * @ClassName CallableThreadPoolTest
  * @Description TODO
  * @Author wangwk-a
  * @Date 2022/1/2 20:06
  * @Version 1.0
  */
 public class CallableThreadPoolTest {
     public static void main(String[] args) {
         ExecutorService service = Executors.newFixedThreadPool(4);
         service.submit(new MyCallableThread());
         service.submit(new MyCallableThread());
         service.submit(new MyCallableThread());
         service.submit(new MyCallableThread());
         service.shutdown();
     }
 }
 ​
 class MyCallableThread implements Callable<Boolean> {
 ​
     @Override
     public Boolean call() throws Exception {
         System.out.println(Thread.currentThread().getName());
         return true;
     }
 }
 Connected to the target VM, address: '127.0.0.1:54363', transport: 'socket'
 pool-1-thread-3
 pool-1-thread-2
 pool-1-thread-4
 pool-1-thread-1
 Disconnected from the target VM, address: '127.0.0.1:54363', transport: 'socket'