1. 单例模式的适用场景

确保任何情况下都绝对只有一个实例

ServletContext,ServletConfig,ApplicationContext,DBPool…

2. 单例模式的常见写法

2.1 饿汉式单例

在单例类首次加载时就创建实例(两种写法)

HungrySingleton

  1. public class HungrySingleton {
  2. private static final HungrySingleton hungrySingleton = new HungrySingleton();
  3. //构造方法私有
  4. private HungrySingleton(){}
  5. //对外提供返回实例
  6. public static HungrySingleton getInstance(){
  7. return hungrySingleton;
  8. }
  9. }

HungryStaticSingleton

  1. public class HungryStaticSingleton {
  2. private static final HungryStaticSingleton hungrySingleton;
  3. static {
  4. hungrySingleton = new HungryStaticSingleton();
  5. }
  6. //构造方法私有
  7. private HungryStaticSingleton(){}
  8. //对外提供返回实例
  9. public static HungryStaticSingleton getInstance(){
  10. return hungrySingleton;
  11. }
  12. }

2.2 懒汉式单例

被外部调用时才创建实例

LazySimpleSingleton (线程不安全)

  1. public class LazySimpleSingleton {
  2. private static LazySimpleSingleton lazy = null;
  3. //构造私有
  4. private LazySimpleSingleton(){}
  5. //创建对象
  6. public static LazySimpleSingleton getInstance(){
  7. if (null == lazy){
  8. lazy = new LazySimpleSingleton();
  9. }
  10. return lazy;
  11. }
  12. }

保证安全模式下加上
synchronized 关键字

  1. public class LazySimpleSingleton {
  2. private static LazySimpleSingleton lazy = null;
  3. //构造私有
  4. private LazySimpleSingleton(){}
  5. //创建对象
  6. public synchronized static LazySimpleSingleton getInstance(){
  7. if (null == lazy){
  8. lazy = new LazySimpleSingleton();
  9. }
  10. return lazy;
  11. }
  12. }

LazyInnerClassSingleton 内部类方式单例 性能最优

  1. //全程没有用到synchronized关键字
  2. //
  3. public class LazyInnerClassSingleton {
  4. private LazyInnerClassSingleton(){}
  5. //懒汉式单例
  6. //LazyHolder里面的逻辑需要外部方法调用时才执行
  7. //巧妙了使用了内部类的特性
  8. //JVM底层的实现逻辑
  9. //完美的避免了线程安全逻辑
  10. public static final LazyInnerClassSingleton getInstance(){
  11. return LazyHolder.LAZY;
  12. }
  13. private static class LazyHolder{
  14. private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
  15. }
  16. }

构造方法私有,逃不过反射的法眼!!!

LazyInnerClassSingletonTest 测试类

  1. public class LazyInnerClassSingletonTest {
  2. public static void main(String[] args) {
  3. Class<?> clazz = LazyInnerClassSingleton.class;
  4. try {
  5. Constructor<?> c = clazz.getDeclaredConstructor(null);
  6. c.setAccessible(true);//强制访问
  7. Object o1 = c.newInstance();
  8. Object o2 = LazyInnerClassSingleton.getInstance();
  9. System.out.println(o1 == o2);
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }

可以发现o1 == o2为false,所以需要在new对象的时候进行条件判断!

  1. private LazyInnerClassSingleton(){
  2. if (LazyHolder.LAZY != null){
  3. throw new RuntimeException("不允许构建多个实例!");
  4. }
  5. }

使用序列化方式破解单例方法

SeriableSingleton 序列化破解方式

  1. public class SeriableSingleton implements Serializable {
  2. //反序列化
  3. //将已经序列化的字节码内容,转化为IO流
  4. //通过IO的读取,进而将读取的内容转换为java对象
  5. //在转换过程中会重新创建对象new
  6. public static final SeriableSingleton INSTANCE = new SeriableSingleton();
  7. private SeriableSingleton(){}
  8. public static SeriableSingleton getInstance(){
  9. return INSTANCE;
  10. }
  11. //解决实例化对象不一致的问题 重写方法
  12. //重写了readResolve方法,只不过是覆盖了反序列化出来的对象
  13. //还是创建了两次 发生在JVM层面 相对来说 比较安全
  14. //之前反序列化出来的对象会被GC回收
  15. private Object readResolve(){
  16. return INSTANCE;
  17. }
  18. }

SeriableSingletonTest 测试类

  1. public class SeriableSingletonTest {
  2. public static void main(String[] args) {
  3. SeriableSingleton s1 = null;
  4. SeriableSingleton s2 = SeriableSingleton.getInstance();
  5. FileOutputStream fos = null;
  6. try {
  7. fos = new FileOutputStream("SeriableSingleton.obj");
  8. ObjectOutputStream oos = new ObjectOutputStream(fos);
  9. oos.writeObject(s2);
  10. oos.flush();
  11. oos.close();
  12. FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
  13. ObjectInputStream ois = new ObjectInputStream(fis);
  14. s1 = (SeriableSingleton) ois.readObject();
  15. ois.close();
  16. System.out.println(s1);
  17. System.out.println(s2);
  18. System.out.println(s1 == s2);
  19. }catch (Exception e){
  20. e.printStackTrace();
  21. }
  22. }
  23. }

image.png
源码实例化会去寻找resolve方法

2.3 注册式单例

将每一个实例都缓存到统一的容器中,使用唯一标识获取实例

枚举式单例可以避免反序列化多实例的问题!!!

EnumSingleton 枚举式单例

  1. public enum EnumSingleton {
  2. INSTACNE;
  3. private Object data;
  4. public Object getData() {
  5. return data;
  6. }
  7. public void setData(Object data) {
  8. this.data = data;
  9. }
  10. public static EnumSingleton getInstance(){ return INSTACNE; }
  11. }

EnumSingletonTest 反序列化测试

  1. public class EnumSingletonTest {
  2. public static void main(String[] args) {
  3. EnumSingleton s1 = null;
  4. EnumSingleton s2 = EnumSingleton.getInstance();
  5. s2.setData(new Object());
  6. FileOutputStream fos = null;
  7. try {
  8. fos = new FileOutputStream("EnumSingleton.obj");
  9. ObjectOutputStream oos = new ObjectOutputStream(fos);
  10. oos.writeObject(s2);
  11. oos.flush();
  12. oos.close();
  13. FileInputStream fis = new FileInputStream("EnumSingleton.obj");
  14. ObjectInputStream ois = new ObjectInputStream(fis);
  15. s1 = (EnumSingleton) ois.readObject();
  16. ois.close();
  17. System.out.println(s1);
  18. System.out.println(s2);
  19. System.out.println(s1.getData() == s2.getData());
  20. }catch (Exception e){
  21. e.printStackTrace();
  22. }
  23. }
  24. }

输出结果s1==s2 为 true,避免了反序列化的多实例!!!
这里给大家展示一个上述反编译之后的代码
EnumSingleton

  1. // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
  2. // Jad home page: http://www.kpdus.com/jad.html
  3. // Decompiler options: packimports(3)
  4. // Source File Name: EnumSingleton.java
  5. package com.tiansu.shejimoshi.singleton.register;
  6. public final class EnumSingleton extends Enum
  7. {
  8. public static EnumSingleton[] values()
  9. {
  10. return (EnumSingleton[])$VALUES.clone();
  11. }
  12. public static EnumSingleton valueOf(String name)
  13. {
  14. return (EnumSingleton)Enum.valueOf(com/tiansu/shejimoshi/singleton/register/EnumSingleton, name);
  15. }
  16. private EnumSingleton(String s, int i)
  17. {
  18. super(s, i);
  19. }
  20. public Object getData()
  21. {
  22. return data;
  23. }
  24. public void setData(Object data)
  25. {
  26. this.data = data;
  27. }
  28. public static EnumSingleton getInstance()
  29. {
  30. return INSTACNE;
  31. }
  32. public static final EnumSingleton INSTACNE;
  33. private Object data;
  34. private static final EnumSingleton $VALUES[];
  35. static
  36. {
  37. INSTACNE = new EnumSingleton("INSTACNE", 0);
  38. $VALUES = (new EnumSingleton[] {
  39. INSTACNE
  40. });
  41. }
  42. }

这里IO在反序列化的时候读取的对象源码
image.png
源码中使用一个类名和枚举名确定一个枚举值!!!

ContainerSingleton 容器式注册单例

  1. public class ContainerSingleton {
  2. private ContainerSingleton(){}
  3. private static Map<String, Object> ioc = new ConcurrentHashMap<>();
  4. public static Object getBean(String className){
  5. if (!ioc.containsKey(className)){
  6. Object obj = null;
  7. try {
  8. obj = Class.forName(className).newInstance();
  9. ioc.put(className, obj);
  10. return obj;
  11. }catch (Exception e){
  12. e.printStackTrace();
  13. }
  14. }
  15. return ioc.get(className);
  16. }
  17. }

ConcurrentExecutor 线程执行类

  1. public class ConcurrentExecutor {
  2. /**
  3. * @param runHandler
  4. * @param executeCount 发起请求总数
  5. * @param concurrentCount 同时并发执行的线程数
  6. * @throws Exception
  7. */
  8. public static void execute(final RunHandler runHandler,int executeCount,int concurrentCount) throws Exception {
  9. ExecutorService executorService = Executors.newCachedThreadPool();
  10. //控制信号量,此处用于控制并发的线程数
  11. final Semaphore semaphore = new Semaphore(concurrentCount);
  12. //闭锁,可实现计数量递减
  13. final CountDownLatch countDownLatch = new CountDownLatch(executeCount);
  14. for (int i = 0; i < executeCount; i ++){
  15. executorService.execute(new Runnable() {
  16. public void run() {
  17. try{
  18. //执行此方法用于获取执行许可,当总计未释放的许可数不超过executeCount时,
  19. //则允许同性,否则线程阻塞等待,知道获取到许可
  20. semaphore.acquire();
  21. runHandler.handler();
  22. //释放许可
  23. semaphore.release();
  24. }catch (Exception e){
  25. e.printStackTrace();
  26. }
  27. countDownLatch.countDown();
  28. }
  29. });
  30. }
  31. countDownLatch.await();//线程阻塞,知道闭锁值为0时,阻塞才释放,继续往下执行
  32. executorService.shutdown();
  33. }
  34. public interface RunHandler{
  35. void handler();
  36. }
  37. }

ContainerSingleTest 测试类

  1. public class ContainerSingleTest {
  2. public static void main(String[] args) {
  3. try {
  4. ConcurrentExecutor.execute(new ConcurrentExecutor.RunHandler() {
  5. @Override
  6. public void handler() {
  7. Object bean = ContainerSingleton.getBean("com.tiansu.shejimoshi.template.jdbc.Member");
  8. System.out.println(bean);
  9. }
  10. },10,6);
  11. //对象方便管理 属于懒加载
  12. //线程不安全
  13. }catch (Exception e){
  14. e.printStackTrace();
  15. }
  16. }
  17. }

测试结果
image.png
测试结果反应了线程不安全的情况出现,解决办法,可以给实例方法加上关键字syncronized

2.4 ThreadLocal单例

ThreadLocalSingleton

  1. //伪线程安全
  2. public class ThreadLocalSingleton {
  3. private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
  4. new ThreadLocal<ThreadLocalSingleton>(){
  5. @Override
  6. protected ThreadLocalSingleton initialValue() {
  7. return new ThreadLocalSingleton();
  8. }
  9. };
  10. public static ThreadLocalSingleton getInstance(){
  11. return threadLocalInstance.get();
  12. }
  13. }

ExectorThread 线程执行类

  1. public class ExectorThread implements Runnable{
  2. @Override
  3. public void run() {
  4. //LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
  5. ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
  6. System.out.println(Thread.currentThread().getName() + ":" + singleton);
  7. }
  8. }

ThreadLocalSingletonTest 测试类

  1. public class ThreadLocalSingletonTest {
  2. public static void main(String[] args) {
  3. System.out.println(ThreadLocalSingleton.getInstance());
  4. System.out.println(ThreadLocalSingleton.getInstance());
  5. System.out.println(ThreadLocalSingleton.getInstance());
  6. System.out.println(ThreadLocalSingleton.getInstance());
  7. System.out.println(ThreadLocalSingleton.getInstance());
  8. System.out.println(ThreadLocalSingleton.getInstance());
  9. Thread t1 = new Thread(new ExectorThread());
  10. Thread t2 = new Thread(new ExectorThread());
  11. t1.start();
  12. t2.start();
  13. }
  14. }

image.png
线程注册式单例,对于线程内部每次调用只会产生一个对象,保证线程内部的全局唯一,且天生线程安全,底层使用的是当前线程作为key和new出对象作为value的map方式进行存储并且获取。

3. 单例模式的优缺点

优点:

在内存中只有一个实例,减少了内存开销。
可以避免对资源的多重占用。
设置全局访问点,严格控制访问。

缺点:

没有接口,扩展困难。
如果要扩展单例对象,只有修改代码,没有其他途径。

4. 学习单例的重点总结

1)、私有化构造器
2)、保证线程安全
3)、延迟加载
4)、防止序列化和反序列化破坏单例
5)、防御反射攻击单例