• 深入理解Java单例模式和优化多线程任务处理


    单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例,并提供一个全局访问点。

    饿汉模式

    类加载的同时,创建实例。
    在这里插入图片描述

    class  Singleton {
        private static final Singleton instance = new Singleton();
        //将构造方法设为私有,以防止外部通过new关键字创建新的实例。
        private Singleton() {}
        public static Singleton getInstance() {
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 上述代码定义了一个名为Singleton的类。
    • 在类中定义了一个私有的静态常量instance,它是Singleton类的一个唯一实例。
    • 提供了一个公共的静态方法getInstance(),用于获取Singleton类的唯一实例。

    懒汉模式

    类加载的时候不创建实例,第一次使用的时候才进行创建。
    在这里插入图片描述

    单线程版

    class Singleton {
        private static Singleton instance = null;
        private Singleton() {
        }
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    多线程

    上述单线程代码在多线程中就会出现错误,多个线程同时调用getInstance()方法时,就可能导致创建出多个实例是不安全的。这里我们只需要在getInstance()方法中添加synchronized关键字就可解决。

    class Singleton {
        private static Singleton instance = null;
        private Singleton() {
        }
        public synchronized static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    双重检查锁定

    class Singleton {
        //volatile关键字保证了instance变量在多线程环境下的可见性。
        private static volatile Singleton instance = null;
        private Singleton() {}
        public synchronized static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class){
                    if (instance == null ){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    双重检查可以这样进行理解:
    第一次if先判断实例有没有被创建,如果没被创建就进入第一个if内,使一个线程成功获取锁(其余线程进行阻塞等待),线程获取锁后再次进行判断,判断实例是否创建,没有创建就进行创建。当这个实例创建完了之后,其他竞争到锁的线程就被里层 if 挡住了,也就不会继续创建其他实例。

    阻塞队列

    阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

    • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
    • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

    阻塞队列的一种典型应用场景就是生产者消费者模型。

    在 Java 标准库中内置了阻塞队列。 如果我们需要在一些程序中使用阻塞队列,直接使用标准库中的即可。

    • BlockingQueue 是一个接口,真正实现的类是 LinkedBlockingQueue
    • put 方法用于阻塞式的入队列,take 用于阻塞式的出队列
    • BlockingQueue 也有 offer, poll, peek 等方法, 但这些方法不带有阻塞特性

    下面我们来实现一个阻塞队列:

    • 通过循环队列的方式
    • 使用 synchronized 进行加锁控制
    public class BlockingQueue {
        private int[] arr = new int[1000];
        private volatile int size = 0;
        private int tail = 0;
        private int head = 0;
    
        public void put(int value) throws InterruptedException {
            synchronized (this) {
                while (size == arr.length) {
                    wait();
                }
                arr[tail] = value;
                tail = (tail + 1) % arr.length;
                size++;
                notifyAll();
            }
        }
    
        public int take() throws InterruptedException {
            int ret = 0;
            synchronized (this) {
                while (size == 0) {
                    wait();
                }
                ret = arr[head];
                head = (head + 1) % arr.length;
                size--;
                notifyAll();
            }
            return ret;
        }
    
        public static void main(String[] args) throws InterruptedException {
            BlockingQueue bq = new BlockingQueue();
            Thread t1 = new Thread(() -> {
    
                try {
                    for (int i = 0; i < 10; i++) {
                        bq.put(i);
                        System.out.println("生产者放入:" + i);
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t1.start();
            Thread t2 = new Thread(() -> {
    
                try {
                    for (int i = 0; i < 10; i++) {
                        int num = bq.take();
                        System.out.println("消费者取出:" + num);
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t2.start();
            t1.join();
            t2.join();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    在这里插入图片描述

  • 相关阅读:
    SpringMVC 04 RestFul风格
    [思维][双指针]Klee in Solitary Confinement 2021年ICPC南京站C
    双十一游戏党必备的数码好物有哪些?2022双11游戏党必备外设清单
    MATLAB——径向基神经网络预测程序
    卷十一 汉纪三
    模拟字符串函数
    Mysql数据库基础:DML数据操作语言
    34_ue4进阶末日生存游戏开发[初步拾取功能]
    如何使用Docker安装Kibana
    【JavaWeb】第四章 DOM与正则表达式
  • 原文地址:https://blog.csdn.net/st200112266/article/details/132995333