保证一个类在任何状况下都绝对只有一个实例,而且提供一个全局访问点java
须要隐藏其全部构造方法git
优势:github
在内存中只有一个实例,减小了内存开销json
能够避免对资源的多重占用设计模式
设置全局访问点,严格控制访问缓存
缺点:安全
没有接口,扩展困难jvm
若是要扩展单例对象,只有修改代码,没有别的途径ide
ServletContext性能
ServletConfig
ApplicationContext
DBPool
饿汉式就是在初始化的时候就初始化实例
两种代码写法以下:
public class HungrySingleton { private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton(); private HungrySingleton() { } private static HungrySingleton getInstance() { return HUNGRY_SINGLETON; } }
public class HungryStaticSingleton { private static final HungryStaticSingleton HUNGRY_SINGLETON; static { HUNGRY_SINGLETON = new HungryStaticSingleton(); } private HungryStaticSingleton() { } private static HungryStaticSingleton getInstance() { return HUNGRY_SINGLETON; } }
若是没有使用到这个对象,由于一开始就会初始化实例,这种方式会浪费内存空间
懒汉式单例为了解决上述问题,则是在用户使用的时候才初始化单例
public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { lazySimpleSingleton = new LazySimpleSingleton();//11行 } return lazySimpleSingleton; } }
上述方式,线程不安全,若是两个线程同时进入11行,那么会建立两个对象,须要以下,给方法加锁
public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public synchronized static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { lazySimpleSingleton = new LazySimpleSingleton(); } return lazySimpleSingleton; } }
上述方式虽然解决了线程安全问题,可是整个方法都是锁定的,性能比较差,因此咱们使用方法内加锁的方式解决提升性能
public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { synchronized (LazySimpleSingleton.class) {//11行 lazySimpleSingleton = new LazySimpleSingleton(); } } return lazySimpleSingleton; } }
上述方式若是两个线程同时进入了11行,一个线程a持有锁,一个线程b等待,当持有锁的a线程释放锁以后到return的时候,第二个线程b进入了11行内部,建立了一个新的对象,那么这时候建立了两个线程,对象也并非单例的。因此咱们须要在12行位置增长一个对象判空的操做。
public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { synchronized (LazySimpleSingleton.class) { if (lazySimpleSingleton != null) { lazySimpleSingleton = new LazySimpleSingleton(); } } } return lazySimpleSingleton; } }
上述方式仍是有风险的,由于CPU执行时候会转化成JVM指令执行:
1.分配内存给对象
2.初始化对象
3.将初始化好的对象和内存地址创建关联,赋值
4.用户初次访问
这种方式,在cpu中3步和4步有可能进行指令重排序。有可能用户获取的对象是空的。那么咱们可使用volatile关键字,做为内存屏障,保证对象的可见性来保证咱们对象的单一。
public class LazySimpleSingleton { private static volatile LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { synchronized (LazySimpleSingleton.class) { if (lazySimpleSingleton != null) { lazySimpleSingleton = new LazySimpleSingleton(); } } } return lazySimpleSingleton; } }
还有一种懒汉式单例,利用静态内部类在调用的时候等到外部方法调用时才执行,巧妙的利用了内部类的特性,jvm底层逻辑来完美的避免了线程安全问题
public class LazyInnerClassSingleton { private LazyInnerClassSingleton() { } public static final LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
这种方式虽然可以完美单例,可是咱们若是使用反射的方式以下所示,则会破坏单例
public class LazyInnerClassTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> clazz = LazyInnerClassSingleton.class; Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); Object o1 = c.newInstance(); Object o2 = LazyInnerClassSingleton.getInstance(); System.out.println(o1 == o2); } }
怎么办呢,咱们须要一种方式控制访问者的行为,经过异常的方式去限制使用者的行为,以下所示
public class LazyInnerClassSingleton { private LazyInnerClassSingleton() { throw new RuntimeException("不容许构建多个实例"); } public static final LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
还有一种方式会破坏单例,那就是序列化破坏咱们的单例,以下所示
咱们写一个序列化的方法来尝试一下上述写法是不是知足序列化的。
public class SeriableSingletonTest { public static void main(String[] args) { SeriableSingleton seriableSingleton = SeriableSingleton.getInstance(); SeriableSingleton s2; FileOutputStream fos = null; FileInputStream fis = null; try { fos = new FileOutputStream("d.o"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(seriableSingleton); oos.flush(); oos.close(); fis = new FileInputStream("d.o"); ObjectInputStream ois = new ObjectInputStream(fis); s2 = (SeriableSingleton) ois.readObject(); ois.close(); System.out.println(seriableSingleton); System.out.println(s2); System.out.println(s2 == seriableSingleton); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
为何序列化会破坏单例呢,咱们查看ObjectInputStream的源码
首先,咱们查看ObjectInputStream的readObject方法
查看readObject0方法
查看checkResolve(readOrdinaryObject(unshared)
方法能够看到
红框内三目运算符内若是desc.isInstantiable()
为真就建立新对象,不为空就返回空,此时咱们查看desc.isInstantiable()
方法
此处cons是
若是有构造方法就会返回true,固然咱们一个类必然会有构造方法的,因此这就是为何序列化会破坏咱们的单例
那么怎么办呢,咱们只须要重写readResolve
方法就好了
public class SeriableSingleton implements Serializable { private SeriableSingleton() { throw new RuntimeException("不容许构建多个实例"); } public static final SeriableSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final SeriableSingleton LAZY = new SeriableSingleton(); } private Object readResolve() { return getInstance(); } }
为何重写这个readResolve
的方法就可以避免序列化破坏单例呢
回到上述readOrdinaryObject
方法,能够看到有一个hasReadResolveMethod
方法
点进去
能够看到 readResolveMethod在此处赋值
也就是咱们若是类当中有此方法则在hasReadResolveMethod当中返回的是true
那么会进入readOrdinaryObject
的以下部分
而且以下所示,调用咱们的readResolve
方法获取对象,来保证咱们对象是单例的
可是重写readResolve方法,只不过是覆盖了反序列化出来的对象,可是仍是建立了两次,发生在JVM层面,相对来讲比较安全,以前反序列化出来的对象会被GC回收
枚举式单例属于注册式单例,他把每个实例都缓存到统一的容器中,使用惟一标识获取实例。也是比较推荐的一种写法,以下所示:
public enum EnumSingleton { INSTANCE; private Object data; public static EnumSingleton getInstance() { return INSTANCE; } }
反编译上述文件,能够看到
那么序列化能不能破坏枚举呢
在ObjectInputStream的readObject方法中有针对枚举的判断
上述经过一个类名和枚举名字值来肯定一个枚举值。从而枚举在序列化上是不会破坏单例的。
咱们尝试使用反射来建立一个枚举对象
public enum EnumSingleton { INSTANCE; private Object data; EnumSingleton() { } public static EnumSingleton getInstance() { return INSTANCE; } public static void main(String[] args) { Class clazz = EnumSingleton.class; try { Constructor c = clazz.getDeclaredConstructor(String.class, int.class); c.newInstance("dd", 1); } catch (Exception e) { e.printStackTrace(); } } }
抛出异常
查看Constructor源码能够看到
能够看到jdk层面若是判断是枚举会抛出异常,因此枚举式单例是一种比较推荐的单例的写法。
这种方式是经过容器的方式来保证咱们对象的单例,常见于Spring的IOC容器
public class ContainerSingleton { private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getBean(String className) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance();//12 ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } return ioc.get(className); } public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(100); final CountDownLatch countDownLatch = new CountDownLatch(1000); for (int i = 0; i < 1000; i++) { executorService.submit(new Runnable() { @Override public void run() { Object o = ContainerSingleton.getBean("com.zzjson.singleton.register.ContainerSingleton"); System.out.println(o + ""); countDownLatch.countDown(); } }); } countDownLatch.await(); executorService.shutdown(); } }
这种方式测试可见
出现了几回不一样对象的状况由于咱们线程在12行可能同时进入,这时候咱们须要加一个同步锁以下,这样建立对象才是只会建立一个的
public class ContainerSingleton { private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getBean(String className) { synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } } return ioc.get(className); } public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(100); final CountDownLatch countDownLatch = new CountDownLatch(1000); for (int i = 0; i < 1000; i++) { executorService.submit(new Runnable() { @Override public void run() { Object o = ContainerSingleton.getBean("com.zzjson.singleton.register.ContainerSingleton"); System.out.println(o + ""); countDownLatch.countDown(); } }); } countDownLatch.await(); executorService.shutdown(); } }
这种方式只可以保证在当前线程内的对象是单一的
public class ThreadLocalSingleton { private ThreadLocalSingleton() { } private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private static ThreadLocalSingleton getInstance() { return threadLocalInstance.get(); } }
文中源码地址设计模式