zoukankan      html  css  js  c++  java
  • 二、单例模式之单例模式

    单例模式创建方式有以下几种方式:

    1. 饿汉模式
    2. 懒汉模式
    3. 注册式模式
    4. 枚举式模式
    5. 序列化模式

    1.饿汉模式

    在类加载时初始化,也是利用类加载线程安全的特性确保了单例实例化的线程安全。

    package com.kancy.pattern.single;
    
    /**
     * 单例模式 - 饿汉模式
     * @author kancy
     */
    public class HungerSingleton {
        /**
         * 在类加载时初始化好单例对象,利用类加载来保证单例初始化的线程安全
         * 优点:不用任何锁就能保证绝对线程安全,执行效率高
         * 缺点:由于类加载时就分配内存空间,初始化。如果以后使用不到改对象,就会造成一定程度内存空间的浪费。
         */
        private static HungerSingleton ourInstance = new HungerSingleton();
        private HungerSingleton() {
        }
        public static HungerSingleton getInstance() {
            return ourInstance;
        }
    }

    2.懒汉模式

    1)方式一:

    package com.kancy.pattern.single;
    
    /**
     * 单例模式 - 懒汉模式01
     * @author kancy
     */
    public class LazySingleton01 {
        /**
         * 单例被使用时才创建实例对象,不会造成内存空间浪费,但会存着数据安全问题:
         *      两个线程同一时刻判断instance == null 时,都会去创建对象,后者会覆盖前者引用。
         */
        private static LazySingleton01 instance;
        private LazySingleton01() {
        }
        public static LazySingleton01 getInstance(){
            if(instance == null){
                instance = new LazySingleton01();
            }
            return instance;
        }
    }

    2)方式二:

    package com.kancy.pattern.single;
    
    /**
     * 单例模式 - 懒汉模式02
     * @author kancy
     */
    public class LazySingleton02 {
        /**
         * 单例被使用时才创建实例对象,不会造成内存空间浪费,但会存着数据安全问题:
         *      两个线程同一时刻判断instance == null 时,都会去创建对象,后者会覆盖前者引用。
         *  加上方法可见锁,确保线程安全,但同时会较低执行效率,性能下降。
         */
        private static LazySingleton02 instance;
        private LazySingleton02() {
        }
        public synchronized static LazySingleton02 getInstance(){
            if(instance == null){
                instance = new LazySingleton02();
            }
            return instance;
        }
    }

    3)方式三:

    package com.kancy.pattern.single;
    
    /**
     * 单例模式 - 懒汉模式03
     * @author kancy
     */
    public class LazySingleton03 {
        /**
         * 单例被使用时才创建实例对象,不会造成内存空间浪费,但会存着数据安全问题:
         *      两个线程同一时刻判断instance == null 时,都会去创建对象,后者会覆盖前者引用。
         *  使用对象锁,缩小锁的范围,确保线程安全的同时,减小性能的损失
         */
        private static volatile LazySingleton03 instance; // 保证对象在内存中的可见性
        private LazySingleton03() {
        }
        public static LazySingleton03 getInstance(){
            if(instance == null){
                synchronized (LazySingleton03.class){
                    // double check,防止对象没有初始化完整时,第二个线程再次进来进行初始化
                    if(instance == null){
                        instance = new LazySingleton03();
                    }
                }
            }
            return instance;
        }
    }

    4)方式四:

    package com.kancy.pattern.single;
    
    import java.util.concurrent.atomic.AtomicBoolean;
    
    /**
     * 单例模式 - 懒汉模式04
     * @author kancy
     */
    public class LazySingleton04 {
        /**
         * 可以通过反射修改initFlg再次进行反射初始化对象,目前没有好的办法解决
         *  但一般没人这么无聊吧!!!
         */
        private static AtomicBoolean initFlg = new AtomicBoolean(false);
        private LazySingleton04() {
            // 防止反射入侵:不允许再次创建实例
            if(!initFlg.get()){
                initFlg.set(true);
            }else{
                throw new RuntimeException("单例被反射入侵");
            }
        }
        /**
         * 利用内部类加载的机制巧妙的实现单例的线程安全。
         * 内部类在使用时才会进行类加载。
         * 当调用getInstance方法时,才会把内部类LazySingleton04Holder加载到内存,同时线程安全的初始化LazySingleton04实例。
         * 这样既可以保证线程安全,也可以减少内存浪费的机会。
         */
        public static LazySingleton04 getInstance(){
            return LazySingleton04Holder.instance;
        }
        private static class LazySingleton04Holder{
            private static final LazySingleton04 instance = new LazySingleton04();
        }
    }

    效率从高到低:LazySingleton04 - > LazySingleton01 -> LazySingleton03 -> LazySingleton02,所以推荐使用第四种。

    3.注册式

    把单例注册到容器中,有则取出,无则创建并注册

    package com.kancy.pattern.single;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * 单例模式 - 注册式单例模式
     */
    public class RegisterSingleton {
        /**
         * 读取实现线程安全
         */
        private static Map<String,Object> ioc = new ConcurrentHashMap();
        public static Object getBean(String className){
            if(!ioc.containsKey(className)){
                try {
                    // 创建对象,并且注册到容器当中
                    Class<?> aClass = Class.forName(className);
                    Object instance = aClass.newInstance();
                    // 创建对象到put进入ioc中需要一段时间,可能造成线程不安全
                    ioc.put(className, instance);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return ioc.get(className);
        }
    }

    4.枚举式

    package com.kancy.pattern.single;
    /**
     * 单例模式 - 常量/枚举式单例模式
     */
    public enum EnumSingleton {
        A,B,C;
    }

    5.序列化方式

    序列化:将内存状态转化为字节数组,持久化到磁盘等。

    反序列化:将持久化内容转化为java对象。

    附上测试类代码:

    package com.kancy.pattern.single;
    
    import com.kancy.bean.Person;
    
    import java.io.Serializable;
    import java.util.concurrent.CountDownLatch;
    
    public class SingletonTest implements Serializable,Cloneable{
    
        public static void main(String[] args) {
            testDataSecure();
        }
        /**
         * 测试数据安全
         */
        private static void testDataSecure() {
            int threadCount = 20;
            CountDownLatch count = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    try {
                        // 等待所有线程都创建好,一起去执行
                        count.await();
                        Object bean = RegisterSingleton.getBean(Person.class.getName());
                        System.out.println(bean);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
                // 线程就绪
                count.countDown();
            }
        }
        /**
         * 懒汉效率从高到底:
         *  LazySingleton04 - > LazySingleton01 -> LazySingleton03 -> LazySingleton02
         */
        private static void testEfficiency() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                LazySingleton04.getInstance();
            }
            long end = System.currentTimeMillis();
            System.out.println("用时:" + (end - start));
        }
    }

    kancy
  • 相关阅读:
    jvm 虚拟机参数_新生代内存分配
    jvm 虚拟机参数_堆内存分配
    Xshell 安装 Xftp
    使用 Xshell 连接 linux 系统
    linux 常用命令
    java JSON 和 Object 相互转换
    vsftp实现只能上传不能下载、删除权限配置
    从返回的HTTP Header信息中隐藏Apache的版本号及PHP的X-Powered-By信息
    在SecureCRT中无需输入密码登录Linux主机
    ssh 设置私钥实现两台linux主机无密码访问
  • 原文地址:https://www.cnblogs.com/kancy/p/10226960.html
Copyright © 2011-2022 走看看