单例模式之单例模式

Posted kancy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式之单例模式相关的知识,希望对你有一定的参考价值。

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

  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));
    }
}

以上是关于单例模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之单例模式

设计模式之单例模式以及简单代码实现

Java设计模式之单例模式

设计模式之单例模式

设计模式之单例模式

设计模式之单例模式