单例模式(饿汉方式,懒汉方式),阻塞队列

单例模式的作用

单例模式:全局唯一并且所有程序都可以使用的对象,就是单例模式。

单例模式的两种实现方式:

  1. 饿汉方式(线程安全的)
  2. 懒汉方式

一、单例模式之饿汉方式

public class ThreadDemo84 {

    //单例类
    static class Singleton{
        //单例模式的3个步骤

        //1.将构造函数设置为私有的,不让外部可见(单例模式是不可以new的)

        /**
         * java 默认的构造函数是public的,所以要显示将构造方法设置为私有的,这样才可以将将public的构造方法覆盖掉
         */
        private Singleton(){
        }

        //2.创建私有的静态的类变量(让底下的第三步方法返回)
        private static Singleton singleton = new Singleton();

        //3.给外部提供的获取单例的方法
        //单例不能new,所以使用static
        public static Singleton getInstance() {
            return singleton;
        }
    }

    static Singleton s1 = null;
    static Singleton s2 = null;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s1 = Singleton.getInstance();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                s2 = Singleton.getInstance();
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println(s1 == s2);  //如果得到的是true,说明单例模式的书写是正确的
    }
}
true

饿汉方式:

  • 优点:实现简单,不存在线程安全的问题,因为饿汉的方式是随着程序的启动而初始化的,因为类加载是线程安全的,所以他是线程安全的
  • 缺点:随着程序的启动而启动,有可能整个程序的运行周期里面都没有用到,这样就带来了不必要的开销

二、单例模式之懒汉方式

懒汉方式:不见兔子不撒鹰,他不会随着程序的启动而启动,而是等到有人调用它的时候,它才会初始化

/**
 * 单例模式之懒汉方式1(这里的代码是线程不安全的)
 */
public class ThreadDemo85 {

    static class Singleton{
        //1.设置私有的构造函数,防止其他程序进行创建
        private Singleton(){}

        //2.提供一个私有的静态变量
        //因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
        private static Singleton singleton = null;

        //3.提供给外部调用(返回一个单例对象给外部)
        public static Singleton getInstance() {
            if (singleton == null) {
                //第一次访问,进行实例化,并且只实例化一次
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    private static Singleton s1 = null;
    private static Singleton s2 = null;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //调用单例对象
                s1 = Singleton.getInstance();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                s2 = Singleton.getInstance();
            }
        });
        t2.start();

        //等待线程执行结束
        t1.join();
        t2.join();
        System.out.println(s1 == s2);   
    }
}

true

但是!!!上面的代码会存在一定的问题,上面的代码是线程不安全的,做一下修改就可以看出来线程不安全的问题的存在了。修改的代码见下面:

public class ThreadDemo86 {

    static class Singleton{
        //1.设置私有的构造函数,防止其他程序进行创建
        private Singleton(){}

        //2.提供一个私有的静态变量
        //因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
        private static Singleton singleton = null;

        //3.提供给外部调用(返回一个单例对象给外部)
        public static Singleton getInstance() throws InterruptedException {
            if (singleton == null) {
                Thread.sleep(1000);
                //第一次访问,进行实例化,并且只实例化一次
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    private static Singleton s1 = null;
    private static Singleton s2 = null;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //调用单例对象
                try {
                    s1 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    s2 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();

        //等待线程执行结束
        t1.join();
        t2.join();
        System.out.println(s1 == s2);
    }
}
false

上面的结果是false,就说明不是线程安全的,线程不安全的原因就是两个线程同时进行操作,都操作到要创建单例模式的那一步的时候进行休眠了,这个时候判断的两个线程都是第一次创建单例模式,就会导致线程不安全的问题。

那么如何将上面的线程不安全的代码修改为线程安全的代码呢?见下面的代码:

/**
 * 单例模式之懒汉方式2
 */
public class ThreadDemo87 {

    static class Singleton{
        //1.设置私有的构造函数,防止其他程序进行创建
        private Singleton(){}

        //2.提供一个私有的静态变量
        //因为是懒汉方式,比较懒,所以先不会进行new对象而是设置为null
        private static Singleton singleton = null;

        //3.提供给外部调用(返回一个单例对象给外部)
        //加锁保证线程安全!!!!!!
        public static synchronized Singleton getInstance() throws InterruptedException {
            if (singleton == null) {
                Thread.sleep(1000);  //可以不加
                //第一次访问,进行实例化,并且只实例化一次
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    private static Singleton s1 = null;
    private static Singleton s2 = null;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //调用单例对象
                try {
                    s1 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    s2 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();

        //等待线程执行结束
        t1.join();
        t2.join();
        System.out.println(s1 == s2);
    }
}
true

说明加上锁以后就变成了线程安全的,但是上面的修改方案粒度太多,性能不高

所以使用 双重校验锁来解决:

public class ThreadDemo88 {

    static class Singleton{
        //1.设置私有的构造函数,防止其他程序进行创建
        private Singleton(){}

        //2.提供一个私有的静态变量
        //因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
        private static Singleton singleton = null;

        //3.提供给外部调用(返回一个单例对象给外部)
        //加锁保证线程安全!!!!!!
        public static Singleton getInstance() throws InterruptedException {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    //双重校验锁  上面的一行代码和下面的一行代码
                    if (singleton == null) {
                        //第一次访问,进行实例化,并且只实例化一次
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

    private static Singleton s1 = null;
    private static Singleton s2 = null;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //调用单例对象
                try {
                    s1 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    s2 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();

        //等待线程执行结束
        t1.join();
        t2.join();
        System.out.println(s1 == s2);
    }
}
true

但是上面的版本不是最终的版本,因为在jvm(会进行优化,导致指令重排序)里面还是存在一定的问题,但是代码演示不出来:

在JVM里面可能会进行指令重排序或者

要解决上面的指令重排序的问题只需要加上关键字volatile,下面的代码就是懒汉方式的最终版本了。

public class ThreadDemo89 {

    //单例模式的最终版本!!!!!
    static class Singleton{
        //1.设置私有的构造函数,防止其他程序进行创建
        private Singleton(){}

        //2.提供一个私有的静态变量
        //因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
        //加上关键字volatile防止发生指令重排序的问题
        private static volatile Singleton singleton = null;

        //3.提供给外部调用(返回一个单例对象给外部)
        public static Singleton getInstance() throws InterruptedException {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    //双重校验锁
                    if (singleton == null) {
                        //第一次访问,进行实例化,并且只实例化一次
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

    private static Singleton s1 = null;
    private static Singleton s2 = null;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //调用单例对象
                try {
                    s1 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    s2 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();

        //等待线程执行结束
        t1.join();
        t2.join();
        System.out.println(s1 == s2);
    }
}

true

三、自定义阻塞队列

import java.util.Random;

/**
 * 数组的方式实现阻塞式队列
 */
public class ThreadDemo91 {

    //自定义阻塞队列
    static class MyBlockQueue{
        private int[] values;   //存放数据的数组
        private int first;
        private int last;
        private int size;

        public MyBlockQueue(int maxSize) {
            values = new int[maxSize];
            size = 0;
            first = 0;
            last = 0;
        }


        /**
         * 添加元素,将元素添加到队尾
         * @param val
         */
        public void offer (int val) throws InterruptedException {
            synchronized (this) {
                //判断容量是否达到最大值
                if (size == values.length) {
                    //阻塞等待消费者先消费
                    this.wait();
                }
                values[last++] = val;
                size++;

                //判断是否是最后一个元素‘
                if (last == values.length) {
                    //循环队列
                    last = 0;
                }
                //唤醒消费者取队列中的信息
                this.notify();  //唤醒另一个
            }
        }

        /**
         * 取队首元素
         * @return
         */
        public int poll() throws InterruptedException {
            int result = 0;
            synchronized (this) {
                //判断队列里面是否有元素
                if (size == 0) {
                    //阻塞等待
                    this.wait();
                }
                result = values[first++];
                size--;

                //判断队首是不是最后一个元素
                if (first == values.length) {
                    first = 0;
                }
                this.notify();  //唤醒生产者生产数据
            }
            return result;
        }
    }


    public static void main(String[] args) {

        MyBlockQueue myBlockQueue = new MyBlockQueue(100);

        //生产者生产数据
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {

                //添加数据
                while (true) {
                    int num = new Random().nextInt(10);
                    System.out.println("生产数据:"+num);
                    try {
                        myBlockQueue.offer(num);  //存入数据
                        Thread.sleep(500);   //休眠500ms,添加数据的速度远远慢于取数据的速度
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t1.start();


        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        int result = myBlockQueue.poll();
                        System.out.println("消费数据:" + result);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t2.start();
    }
}
生产数据:6
消费数据:6
生产数据:6
消费数据:6
生产数据:9
消费数据:9
生产数据:9
消费数据:9
生产数据:3
消费数据:3
......
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java单例模式包括饿汉式和懒汉式两种实现方式饿汉式是在类加载阶段就创建实例并持有,而懒汉式则是在需要时才创建实例。 饿汉模式是指在类加载阶段就创建出实例的,因此它的实例化过程相对于普通情况要早很多。这也是为什么叫“饿汉”的原因,就像一个饥饿的人对食物没有抵抗力,一下子就开始吃了一样。 懒汉模式是指在需要时才创建实例。这种方式的优点是节省了资源,只有在需要时才会进行实例化。但是它的缺点是在多线程环境下可能会导致多个线程同时创建实例的问题,需要进行额外的线程安全措施来解决这个问题。 总结来说,饿汉式适合在应用启动时就需要创建实例的情况,因为它的实例化过程早于普通情况。而懒汉式适合在需要时才创建实例的情况,可以节省资源。 需要注意的是,单例模式的使用要根据具体的适应场景来决定,不同的情况下选择不同的实现方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java设计模式之单例模式——饿汉式、懒汉式(初了解)](https://blog.csdn.net/m0_68062837/article/details/127307310)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Java多线程案例之单例模式饿汉懒汉)](https://blog.csdn.net/qq_63218110/article/details/128738155)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值