• 单例模式的几种实现方式


    什么是单例模式:

      当系统中某个类对象只需要实例化一次的时候,我们就用单例来实现。所以单例模式就是用来创建独一无二,只能有一个实例的对象的一直实现方式。

    常见的使用场景:

      比如线程池,缓存,连接数据库的Connection等等。

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

    1,饿汉式

    package singleton;
    
    /**
     * @ClassName SingletonDemo1
     * @Description 饿汉式
     * 类加载到内存后,就实例化一个单例对象,JVM保证线程安全
     * 唯一缺点:不管用不用都会进行加载,但是影响不大,实际上是最适用的一种方式
     * @Author liuyi
     * @Date 2020/6/7 12:22
     * @Version 1.0
     */
    public class SingletonDemo1 {
        //两种方式初始化实例,两种方式的效果是一样的
        //静态常量方式
    //    private static final SingletonDemo1 instance = new SingletonDemo1();
        //静态块方式
        private static final SingletonDemo1 instance;
        static {
            instance = new SingletonDemo1();
        }
        private SingletonDemo1(){
    
        }
        public static SingletonDemo1 getInstance(){
            return instance;
        }
    
        public static void main(String[] args) {
            SingletonDemo1 instance1 = SingletonDemo1.getInstance();
            SingletonDemo1 instance2 = SingletonDemo1.getInstance();
            System.out.println(instance1==instance2);
            //返回的结果为true,说明不管取多少次都是同一个实例
        }
    }

    2,懒汉式

    package singleton;
    
    /**
     * @ClassName SingletonDemo2
     * @Description 懒汉式
     * 需要使用该对象的时候再去实例化
     * 缺点:会产生线程安全问题
     * @Author liuyi
     * @Date 2020/6/7 13:23
     * @Version 1.0
     */
    public class SingletonDemo2 {
        private static SingletonDemo2 instance;
    
        private SingletonDemo2(){
    
        }
    
        public static SingletonDemo2 getInstance(){
            if(instance==null){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new SingletonDemo2();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            //为什么说线程不安全
            //因为使用了全局的静态变量,会造成多线程访问的时候会产生不唯一的实例
            for (int i = 0; i <100 ; i++) {
                new Thread(()->{
                    System.out.println(getInstance());
                }).start();
            }
            //打印的对象完全乱了,根本不是同一个实例
        }
    }

    3,饿汉式(加锁)

    package singleton;
    
    /**
     * @ClassName SingletonDemo2
     * @Description 懒汉式(加锁)
     * 缺点:效率低
     * @Author liuyi
     * @Date 2020/6/7 13:23
     * @Version 1.0
     */
    public class SingletonDemo3 {
        private static SingletonDemo3 instance;
    
        private SingletonDemo3(){
    
        }
    
        public synchronized static SingletonDemo3 getInstance(){
            if(instance==null){
                instance = new SingletonDemo3();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            //加了锁解决了懒汉式的线程不安全问题,但是这样效率就会明显降低
            for (int i = 0; i <100 ; i++) {
                new Thread(()->{
                    System.out.println(getInstance());
                }).start();
            }
        }
    }

    4,双重检查

    package singleton;
    
    /**
     * @ClassName SingletonDemo2
     * @Description 双重检查(比较完美的写法)
     * @Author liuyi
     * @Date 2020/6/7 13:23
     * @Version 1.0
     */
    public class SingletonDemo4 {
        //必须加volatile关键字,防止指令重排
        private static volatile SingletonDemo4 instance;
    
        private SingletonDemo4(){
    
        }
    
        public synchronized static SingletonDemo4 getInstance(){
            //为什么要进行双重检查
            //比如两个线程同时进入该方法,都拿到instance为空,其中一个拿到锁并new了一个实例,
            //此时另外一个线程它并不知道你已经new了实例,所以当它拿到锁之后会继续new一个实例
            //所以如果在锁里面继续判断一次是很有必须要的
            if(instance==null){
                synchronized (SingletonDemo4.class){
                    if(instance==null){
                        instance = new SingletonDemo4();
                    }
                }
            }
            return instance;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i <100 ; i++) {
                new Thread(()->{
                    System.out.println(getInstance());
                }).start();
            }
        }
    }

    5,静态内部类方式

    package singleton;
    
    /**
     * @ClassName SingletonDemo5
     * @Description 静态内部类方式
     * JVM保证线程安全
     * 加载外部类是不会加载内部类,实现了懒加载
     * 最完美的写法
     * @Author liuyi
     * @Date 2020/6/7 13:52
     * @Version 1.0
     */
    public class SingletonDemo5 {
    
        private SingletonDemo5(){
    
        }
        private static class SingletonDemo5Inside{
            private static final SingletonDemo5 instance = new SingletonDemo5();
        }
    
        public static SingletonDemo5 getInstance(){
            return SingletonDemo5Inside.instance;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i <100 ; i++) {
                new Thread(()->{
                    System.out.println(SingletonDemo5.getInstance());
                }).start();
            }
        }
    }

    6,枚举方式

    package singleton;
    
    /**
     * @Author liuyi
     * @Description 枚举单例
     * 不仅可以解决线程同步,还可以防止反序列化(因为枚举类没有构造方法)
     * @Date 17:28 2020/6/7
     * @Param  
     * @return 
     **/
    public enum SingletonDemo6 {
    
        instance;
    
        public void test(){
            System.out.println("测试测试");
        }
        public static void main(String[] args) {
            instance.test();
            for (int i = 0; i <100 ; i++) {
                new Thread(()->{
                    System.out.println(SingletonDemo6.instance);
                }).start();
            }
        }
    }

    总结:

      这几种实现单例的方式中,枚举单例是最完美的,因为枚举单例可以防止反序列话,也可以防止通过反射的方式去创建实例,但是实际运用中最适用的还是饿汉式。因为既然你使用了单例,为什么还要用反射呢,这样就属于搞破坏了。

  • 相关阅读:
    Redis基本概念、基本使用与单机集群部署
    Storm安装部署
    HBase单机和集群版部署
    Hive基础概念、安装部署与基本使用
    Hadoop — HDFS的概念、原理及基本操作
    随机森林
    深度学习入门: CNN与LSTM(RNN)
    word修改页眉使本页的页眉与别的页不一样
    几个值得学习的Java博客
    【转】求最短路径长度--简单易懂
  • 原文地址:https://www.cnblogs.com/liu-yi/p/13061526.html
Copyright © 2020-2023  润新知