• 《多线程案例》阻塞队列、定时器、线程池、饿汉与懒汉模式


    目录

    一、阻塞队列的模拟实现

    🍑阻塞队列实现思路

    🍑代码

    🍑测试代码

    二、定时器的模拟实现

    三、线程池的模拟实现

    🍑标准库中的线程池

    🍑代码

    🍑 线程池的优点

    四、单例设计模式

    饿汉模式与懒汉模式


    一、阻塞队列的模拟实现

    阻塞队列实现思路

    • 通过 "循环队列" 的方式来实现.
    • 使用 synchronized 进行加锁控制.
    • put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程).
    • take 取出元素的时候,
       

    代码

    1. // 阻塞队列——wait,线程安全——加锁
    2. // 阻塞队列的模拟实现
    3. public class MyBlockQueue {
    4. private int[] array = new int[100];
    5. private int head;
    6. private int tile;
    7. volatile private int size;// 防止内存可见性问题
    8. public MyBlockQueue() {
    9. array = new int[100];
    10. }
    11. // 入队列
    12. public void put(int value) throws InterruptedException {
    13. synchronized (this) {
    14. if (size == array.length) {
    15. this.wait();
    16. }
    17. array[tile] = value;
    18. tile++;
    19. if (tile == array.length) {
    20. tile = 0;
    21. }
    22. size++;
    23. // 但我们的插入结束后,队列就不空了,就应该唤醒我们的队列为空等待(take中的等待)
    24. this.notify(); // 即使没人在等待,多写一个也要等待
    25. }
    26. }
    27. // 出队列
    28. public Integer take() throws InterruptedException {
    29. int ret;
    30. synchronized (this) {
    31. if (size == 0) {
    32. this.wait(); // 在出队列的时候,如果队列为空,就等待
    33. }
    34. ret = array[head];
    35. head++;
    36. size--;
    37. if (head == array.length) {
    38. head = 0;
    39. }
    40. // 当我们出了一个队列后,队列就不满了,唤醒队列为满的等待(put中等待)
    41. this.notify();
    42. }
    43. return ret;
    44. }
    45. }

    测试代码

    1. import java.util.concurrent.LinkedBlockingQueue;
    2. import java.util.concurrent.BlockingDeque;
    3. // 生产者,消费者模型,用到了我们自己模拟实现的阻塞队列
    4. public class BlockingQueue {
    5. public static void main(String[] args) {
    6. // BlockingQueue blockingQueue = new BlockingQueue();
    7. // java.util.concurrent.BlockingQueue queue = new LinkedBlockingQueue<>(); // 阻塞队列
    8. MyBlockQueue queue = new MyBlockQueue();
    9. Thread consumer = new Thread(() -> {
    10. while (true) {
    11. try {
    12. int ret = queue.take();
    13. System.out.println("消费元素" + ret);
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. }
    17. }
    18. }); // 消费者
    19. consumer.start();
    20. Thread producer = new Thread(() -> {
    21. int n = 0;
    22. while (true) {
    23. try {
    24. queue.put(n);
    25. System.out.println("生产元素" + n);
    26. Thread.sleep(300);
    27. } catch (InterruptedException e) {
    28. e.printStackTrace();
    29. }
    30. n++;
    31. }
    32. }); // 生产者
    33. producer.start();
    34. }
    35. }

     

     

     

     

    二、定时器的模拟实现

    1. // 定时器的模拟实现
    2. import java.util.concurrent.PriorityBlockingQueue;
    3. class MyTask implements Comparable{
    4. // 任务要干啥
    5. public Runnable command;
    6. // 任务在什么时候干,任务推迟的时间
    7. public long time;
    8. public MyTask(Runnable command, long after) {
    9. this.command = command;
    10. this.time = System.currentTimeMillis() + after; // 记录下任务执行的绝对时间
    11. }
    12. // 执行任务的run方法,直接在内部调用command的run方法
    13. public void run() {
    14. command.run();
    15. }
    16. public long getTime() {
    17. return time;
    18. }
    19. @Override
    20. public int compareTo(MyTask o) {
    21. return (int) (this.time - o.time);
    22. }
    23. }
    24. // 自己创建的定时器类
    25. class MyTimer {
    26. //
    27. private Object object = new Object();
    28. // 使用优先级阻塞队列来保存要执行的若干个队列,按时间来确定优先级(这是标准库中的)
    29. public static PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
    30. // command要执行的顺序是什么,after在什么时间执行该任务
    31. public void schedule(Runnable command, long after) {
    32. MyTask myTask = new MyTask(command, after);
    33. synchronized (object) {
    34. queue.put(myTask);
    35. object.notify(); // 唤醒当前线程
    36. }
    37. }
    38. public MyTimer() {
    39. // 在这里启动一个线程
    40. Thread t = new Thread(() -> {
    41. while (true) {
    42. // 循环过程中, 就不断的尝试从队列中获取到队首元素.
    43. // 判定队首元素当前的时间是否就绪. 如果就绪了就执行, 不就绪, 就不执行.
    44. synchronized (object) { // 因为线程调度是随机的,可能在任务塞回队列之后,wait之前。有其他的任务加入(也没有成过唤醒该线程)
    45. while (queue.isEmpty()) {
    46. try {
    47. object.wait(); // 在等待过程中,通过唤醒,也可以执行其他任务
    48. } catch (InterruptedException e) {
    49. e.printStackTrace();
    50. }
    51. }
    52. MyTask myTask = null;
    53. try {
    54. myTask = queue.take();
    55. } catch (InterruptedException e) {
    56. e.printStackTrace();
    57. }
    58. // 时间到了
    59. if (myTask.getTime() <= System.currentTimeMillis()) {
    60. myTask.run();
    61. }
    62. // 时间还没到
    63. else {
    64. // 时间还没到, 塞回到队列中
    65. queue.put(myTask);
    66. try { // 当wait陷入等待,thread线程暂时停止执行,但main主线程还可以插入新的任务,thread线程会提前唤醒
    67. object.wait(myTask.getTime() - System.currentTimeMillis()); // 在等的时候,通过唤醒,其他代码也可以执行
    68. } catch (InterruptedException e) {
    69. e.printStackTrace();
    70. }
    71. }
    72. }
    73. }
    74. });
    75. t.start();
    76. }
    77. }
    78. public class TimerTest {
    79. public static void main(String[] args) throws InterruptedException {
    80. MyTimer myTimer = new MyTimer();
    81. myTimer.schedule(new Runnable() {
    82. @Override
    83. public void run() {
    84. System.out.println("3333");
    85. }
    86. }, 6000);
    87. myTimer.schedule(new Runnable() {
    88. @Override
    89. public void run() {
    90. System.out.println("2222");
    91. }
    92. }, 4000);
    93. myTimer.schedule(new Runnable() {
    94. @Override
    95. public void run() {
    96. System.out.println("1111");
    97. }
    98. }, 2000);
    99. }
    100. }

     

    三、线程池的模拟实现

    标准库中的线程池


    使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
    返回值类型为 ExecutorService
    通过 ExecutorService.submit 可以注册一个任务到线程池中

     

     

    代码

    1. // 线程池的模拟实现
    2. import java.util.concurrent.BlockingQueue;
    3. import java.util.concurrent.ExecutorService;
    4. import java.util.concurrent.Executors;
    5. import java.util.concurrent.LinkedBlockingQueue;
    6. class MyThreadPool {
    7. // BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue, java标准库中内置的阻塞队列
    8. private BlockingQueue queue = new LinkedBlockingQueue<>(); //这个队列就是 "任务队列" 把当前线程池要完成的任务都放到这个队列中.
    9. // 再由线程池内部的工作线程负责完成它们.
    10. // 核心方法, 往线程池里插入任务.
    11. public void submit(Runnable runnable) throws InterruptedException {
    12. queue.put(runnable);
    13. }
    14. // 设置线程池中最大的线程数
    15. // 构造方法中,就需要创建一些工作线程,让这些工作线程负责完成上述执行任务的工作
    16. public MyThreadPool(int n) {
    17. for (int i = 0; i < n; i++) {
    18. Thread t = new Thread(() -> {
    19. //Thread.currentThread()返回当前线程对象引用
    20. //.isInterrupted()测试是否当前线程已被中断 中断返回true,否则返回false
    21. //总的说,这句就是无限判断当前线程状态,如果没有中断,就一直执行while内容
    22. while (!Thread.currentThread().isInterrupted()) {
    23. try {
    24. Runnable runnable = queue.take();
    25. runnable.run();
    26. } catch (InterruptedException e) {
    27. e.printStackTrace();
    28. }
    29. }
    30. });
    31. t.start();
    32. }
    33. }
    34. }
    35. public class ThreadPool {
    36. public static void main(String[] args) throws InterruptedException {
    37. MyThreadPool myThreadPool = new MyThreadPool(10); // 这里用的是我们自己模拟实现的线程池
    38. for (int i = 0; i < 5; i++) {
    39. myThreadPool.submit(new Runnable() {
    40. @Override
    41. public void run() {
    42. System.out.println("这是一个任务!");
    43. }
    44. });
    45. }
    46. }
    47. public static void main1(String[] args) {
    48. // 借助静态方法来创建实例,像这样的方法叫做”工厂方法", 所对应的设计模式,就叫做“工厂模式"
    49. // 当前的线程池中最多有10个线程,线程池存在的目的就是未来让程序员不必创建新的线程,直接使用已有的线程完成想要进行的工作
    50. // 为什么要有工程模式,通常情况下,创建对象,是借助new,调用构造方法来实现的。但是C++/java里的构造方法,有着诸多限制,很多时候不方便使用
    51. ExecutorService threadPool = Executors.newFixedThreadPool(10);
    52. // ThreadPoolExcutor线程池原始的类,其实也是有构造方法的。Executor里面的各自工厂方法,其实都是针对TreadPoolExecutor这个类进行了new并传入了不同风格的参数,来达到构造不同种类的线程池的目标
    53. threadPool.submit(new Runnable() { // 通过 ExecutorService.submit 可以注册一个任务到线程池中
    54. @Override
    55. public void run() {
    56. System.out.println("这是一个任务!");
    57. }
    58. });
    59. }
    60. }

     

     

     线程池的优点

    1. 降低资源的消耗。线程本身是一种资源,创建和销毁线程会有CPU开销;创建的线程也会占用一定的内存。(而我们的线程池是把线程创建好了后,就放到池子了,需要用到线程,直接从池子里取就行,不用系统在进行创建。但线程不用了,直接还是放到池子了,不用系统来销毁。)从池子里取(用户态操作)比系统创建线程(内核态操作)来的快
    2. 提高响应速度:当任务来时可以直接使用,不用等待线程创建
    3. 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。

     

    四、单例设计模式

    饿汉模式与懒汉模式

    1. package Thread2;
    2. // 单例设计模式
    3. // 饿汉模式,程序启动立即创建实例,从始至终都是线程安全的
    4. class Singleton {
    5. private static Singleton instance = new Singleton();
    6. public static Singleton getInstance() {
    7. return instance;
    8. }
    9. // 构造方法设为私有,其他的类想来new就不行了
    10. public Singleton() {}
    11. }
    12. // 懒汉模式,程序有需要时候再创建实例,在一开始是不安全的,之后线程安全
    13. class SingletonLazy {
    14. volatile private static SingletonLazy instance = null; // 避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile
    15. public static SingletonLazy getInstance() {
    16. if (instance == null) { // 双重循环,减少锁竞争
    17. synchronized (SingletonLazy.class) {
    18. if (instance == null) {
    19. instance = new SingletonLazy();
    20. }
    21. }
    22. }
    23. return instance;
    24. }
    25. }
    26. public class demo3 {
    27. public static void main(String[] args) {
    28. Singleton.getInstance();
    29. System.out.println(Singleton.getInstance());
    30. }
    31. }

  • 相关阅读:
    计蒜客详解合集(1)期
    主机基本安全加固
    历时8年,自建站最终改版
    象棋中的马跳步问题
    数据结构-堆(完全二叉树)
    关系数据库如何工作
    RK3588平台开发系列讲解(项目篇)嵌入式AI的学习步骤
    47 从前序与中序遍历序列构造二叉树
    某云负载均衡获取客户端真实IP的问题
    DPU — 功能特性 — 安全系统的硬件卸载
  • 原文地址:https://blog.csdn.net/weixin_61061381/article/details/126106678