zoukankan      html  css  js  c++  java
  • 实现单例设计模式的多种方式

    1. 关于单例设计模式

    Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
    单:唯一
    例:实例

    单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
    例如:代表JVM运行环境的Runtime类

    要点:

    (1)某个类只能有一个实例:构造器私有化
    (2)它必须能够创建该类实例:使用该类的静态变量来保存这个唯一的实例。
    (3)它必须能返回该实例:通过get方法返回该实例或直接暴露给外部.

    2. 单例设计模式的种类

    2.1 饿汉式:直接创建对象,不存在线程安全问题

    2.1.1 直接实例化饿汉式(简洁直观)

    /*
     * 饿汉式:
     * 	在类初始化时直接创建实例对象,不管是否需要这个对象都会创建
     * 单例创建要求:
     * (1)构造器私有化
     * (2)自行创建,并且用静态变量保存
     * (3)向外提供这个实例
     * (4)强调这是一个单例,我们可以用final修饰
     */
    public class Singleton1 {
        public static final Singleton1 INSTANCE = new Singleton1();
        private Singleton1(){
    
        }
        public static void main(String[] args) {
            Singleton1 s = Singleton1.INSTANCE;
            System.out.println(s);
        }
    }
    
    

    2.1.2 枚举式(最简洁)

    package com.bigdata.juc.singleton;
    /*
     * 枚举类型:表示该类型的对象是有限的几个
     * 我们可以限定为一个,就成了单例
     */
    enum EnumSingleton{
        INSTANCE
    }
    public class Singleton2 {
        public static void main(String[] args) {
            EnumSingleton s = EnumSingleton.INSTANCE;
            System.out.println(s);
        }
    }
    
    

    2.1.3 静态代码块饿汉式(适合复杂实例化)

    //静态代码块饿汉式
    public class Singleton3 {
        public static final Singleton3 INSTANCE;
        public String info;
        private Singleton3(String info){
            this.info = info;
        }
        static{
            try {
                Properties pro = new Properties();
    
                pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
    
                INSTANCE = new Singleton3(pro.getProperty("info"));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        public static void main(String[] args) {
            Singleton3 s = Singleton3.INSTANCE;
            System.out.println(s.info);
        }
    }
    

    2.2 懒汉式:延迟创建对象

    2.2.1 线程不安全(适用于单线程)

    import java.util.concurrent.*;
    
    /*
     * 懒汉式:
     * 	延迟创建这个实例对象,可能会出现线程不安全的情况
     *
     * (1)构造器私有化
     * (2)用一个静态变量保存这个唯一的实例
     * (3)提供一个静态方法,获取这个实例对象
     */
    public class Singleton4 {
        private static Singleton4 instance;
        private Singleton4(){
    
        }
        public static Singleton4 getInstance(){
            if(instance == null){
    
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                instance = new Singleton4();
            }
            return instance;
        }
        public static void main(String[] args) throws InterruptedException, ExecutionException {
    
            //检查这种创建方式的线程安全性
            Callable<Singleton4> c = new Callable<Singleton4>() {
    
                @Override
                public Singleton4 call() throws Exception {
                    return Singleton4.getInstance();
                }
            };
            //使用newFixedThreadPool线程池,设置线程数为2
            ExecutorService es = Executors.newFixedThreadPool(2);
            Future<Singleton4> f1 = es.submit(c);
            Future<Singleton4> f2 = es.submit(c);
    
            Singleton4 s1 = f1.get();
            Singleton4 s2 = f2.get();
    
            System.out.println(s1 == s2);//false 说明这种创建单例的方式,在多线程的方式下并不安全
            System.out.println(s1);
            System.out.println(s2);
    
            es.shutdown();
    
    //        new Thread(()->{
    //            System.out.println(Thread.currentThread().getName()+":"+Singleton4.getInstance());
    //        },"A").start();
    //        new Thread(()->{
    //            System.out.println(Thread.currentThread().getName()+":"+Singleton4.getInstance());
    //        },"B").start();
    
        }
    }
    

    2.2.2 线程安全(适用于多线程)

    /*
     * 懒汉式:
     * 	延迟创建这个实例对象,针对于线程不安全的问题,可以在创建的时候,使用lock或synchronize来解决
     *
     * (1)构造器私有化
     * (2)用一个静态变量保存这个唯一的实例
     * (3)提供一个静态方法,获取这个实例对象
     */
    public class Singleton5 {
        private static Singleton5 instance;
        private Singleton5(){
    
        }
        //并不推荐使用同步方法的方式,来完成单例模式设计,因为它锁了整个方法,其他线程想要获取实例只能等待,显然效率比较低,推荐使用DCL(Double Check Lock 双端检锁机制)来完成单例设计
        //  public synchronized static Singleton5 getInstance(){
        public static Singleton5 getInstance(){
            if(instance == null){
                //使用同步代码块来解决单例创建过程中的线程不安全问题,也可以使用同步方法来实现
                synchronized (Singleton5.class) {
                    if(instance == null){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        instance = new Singleton5();
                    }
                }
            }
            return instance;
        }
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            //验证多线程环境下,这种创建线程的方式是否安全
            Callable<Singleton5> c = new Callable<Singleton5>() {
    
                @Override
                public Singleton5 call() throws Exception {
                    return Singleton5.getInstance();
                }
            };
    
            ExecutorService es = Executors.newFixedThreadPool(2);
            Future<Singleton5> f1 = es.submit(c);
            Future<Singleton5> f2 = es.submit(c);
    
            Singleton5 s1 = f1.get();
            Singleton5 s2 = f2.get();
    
            System.out.println(s1 == s2);//true
            System.out.println(s1);
            System.out.println(s2);
    
            es.shutdown();
    
        }
    }
    

    注意这里在实现多线程下的单例设计时,尽管使用DCL(Double Check Lock 双端检锁机制),但仍然存在一种问题,是由于指令重排导致的,所以为了防止执行重排,需要在instance上添加volatile修饰符

        //添加volatile禁止指令重排
        private static volatile Singleton5 instance;
    

    所说的问题也就是这样的,由于指令重排,程序未必按照上面的编写顺序执行,当某一个线程在执行时,读取到的instance不为null时,但instanc对象可能没有完成初始化,最终所得到的instance可能还是null,使用时导致空指针异常。
    关于这点介绍,在周志明的《深入理解Java虚拟机 第二版》的P370,DCL单例模式中有详细的解释



    这种编写方式被称为“双重检查锁”,主要在getSingleton()方法中,进行两次null检查。这样可以极大提升并发度,进而提升性能。毕竟在单例中new的情况非常少,绝大多数都是可以并行的读操作,因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,也就提高了执行效率。但是必须注意的是volatile关键字,该关键字有两层语义。第一层语义是可见性,可见性是指在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以其它线程会马上读取到已修改的值,关于工作内存和主内存可简单理解为高速缓存(直接与CPU打交道)和主存(日常所说的内存条),注意工作内存是线程独享的,主存是线程共享的。volatile的第二层语义是禁止指令重排序优化,我们写的代码(特别是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同,这在单线程并没什么问题,然而一旦引入多线程环境,这种乱序就可能导致严重问题。volatile关键字就可以从语义上解决这个问题,值得关注的是volatile的禁止指令重排序优化功能在Java 1.5后才得以实现,因此1.5前的版本仍然是不安全的,即使使用了volatile关键字。或许我们可以利用静态内部类来实现更安全的机

    引用链接:https://www.cnblogs.com/zhanqing/p/11076646.html

    2.2.3 静态内部类形式(适用于多线程)

    /*
     * 在内部类被加载和初始化时,才创建INSTANCE实例对象
     * 静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。
     * 因为是在内部类加载和初始化时,创建的,因此是线程安全的
     */
    public class Singleton6 {
        private Singleton6(){
    
        }
        private static class Inner{
            private static final Singleton6 INSTANCE = new Singleton6();
        }
    
        public static Singleton6 getInstance(){
            return Inner.INSTANCE;
        }
    }
    

    3.总结:

    • 如果是饿汉式,枚举形式最简单
    • 如果是懒汉式,静态内部类形式最简单
  • 相关阅读:
    checkListbox的单选
    IP地址控件CIPAddressCtrl类的使用
    C++ Socket编程步骤
    环形缓冲区
    隐式链接和显示链接的区别
    memset、memcpy的使用方法!
    cetlm下载地址
    安装 GCC
    centos 配置代理
    make软件包安装
  • 原文地址:https://www.cnblogs.com/cosmos-wong/p/11914878.html
Copyright © 2011-2022 走看看