单例模式创建方式有以下几种方式:
- 饿汉模式
- 懒汉模式
- 注册式模式
- 枚举式模式
- 序列化模式
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)); } }