单例模式

单例模式

1.单例模式

  • 单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    • 让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
  • 单例模式的普通实现
package cn.sun.code.twentyone.putong;

/**
 * 单例模式的普通实现
 */
public class Singleton {
	
	private static Singleton instance;

	// 构造方法让其private,这样就堵死了外界利用new创建此类实例的可能
	private Singleton() {
		
	}

	// 此方法是获得本类实例的唯一全局访问点
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}

		return instance;
	}
	
}

  • 单例模式的好处
    • 保证唯一实例
    • 对唯一实例的受控访问:它可以严格地控制客户怎样访问它以及何时访问它。
      • 怎样:只能通过类内部维护的static方法访问

2.单例模式的实现

2.1 懒汉式实现

  • 1中的实现方式即为懒汉式单例
    • 易于分析,该实现方式存在多线程安全问题。一般来说,多线程的安全问题都是由于多个线程执行同一个代码块时,在判断条件处当前线程丧失CPU执行权,由于线程的切换绕过了判断条件从而导致数据错误。懒汉式单例即为最典型,简单的一类多线程错误。
    • 因为线程不安全,所以基本不使用

2.2 饿汉式单例实现

  • 饿汉式单例实现代码
package cn.sun.code.twentyone.ehan;

/**
 * 饿汉式单例
 */
public class EagerSingleton {

	private static EagerSingleton singleton = new EagerSingleton();

	private EagerSingleton() {

	}

	public static EagerSingleton getInstance() {
		return singleton;
	}
}

  • 该种实现方式是不会存在线程安全问题的:
    • 虚拟机采用了CAS配上失败重试的方式保证更新更新操作的原子性和TLAB两种方式来解决这个问题
  • 这种方式有个明显的缺陷:一旦我访问了Singleton的任何其他的静态域,就会造成实例的初始化,而事实是可能我们从始至终就没有使用这个实例,造成内存的浪费。
  • 不过在有些时候,直接初始化单例的实例也无伤大雅,对项目几乎没什么影响,比如我们在应用启动时就需要加载的配置文件等,就可以采取这种方式去保证单例。

2.3 双重检查加锁

  • 双重检查加锁代码实现
package cn.sun.code.twentyone.shuangchongjiancha;

/**
 * 双重检查加锁单例实现
 */
public class DoubleCheckLockSingleton {

	private static DoubleCheckLockSingleton instance;

	private DoubleCheckLockSingleton() {

	}

  // JDK1.6之后对synchronized性能优化了不少
  // 不可避免地还是存在一些性能的问题
	public static DoubleCheckLockSingleton getInstance() {
		if (instance == null) {
			synchronized (DoubleCheckLockSingleton.class) {
				if (instance == null)
					instance = new DoubleCheckLockSingleton();
			}
		}
		return instance;
	}
	
}

  • 第一个判断条件是为了避免每次都判断锁导致效率低下
  • 第二个判断条件是单例的常规操作
    • 同步代码块的存在保证不管CPU执行权如何切换,一个线程都能完整的将代码块中的代码执行完。例如线程A执行到17行时丧失执行权,但是DoubleCheckLockSingleton的锁未被释放,其他线程即使获得CPU执行权也无法进入同步代码块,等到线程A再次获得执行权后将代码块中的代码执行完毕,在内存中创建一个DoubleCheckLockSingleton实例后,将锁释放。其他线程再进入同步代码块中,判断条件if (instance == null)为false,无法再重复创建实例。
    • 此种方式可以可以解决if (instance == null)处因当前执行CPU的线程切换导致的线程安全问题。
  • 指令重排的问题:
    • CPU执行的时候回转换成JVM指令执行
      1. 内存分配给这个对象
      2. 初始化对象
      3. 将初始化好的对象和内存地址建立关联,赋值

2.4 静态内部类实现单例模式

package cn.sun.pattern.single;

/**
 * @author : ssk
 * @date : 2020/1/13
 */
public class LazyInnerClassSingleton {

  private LazyInnerClassSingleton(){}

  public static final LazyInnerClassSingleton getInstance() {
    return LazyHolder.lazy;
  }

  // LazyHolder里面的逻辑需要等到外部方法调用时才执行
  // 巧妙利用了内部类的特性
  // JVM底层底层逻辑,完美避免了线程安全问题
  private static class LazyHolder {

    private static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();

  }

}

  • 这种方式不能防止反射暴力创建对象:(同时上述双检锁方式也能通过反射的方式新建实例)
package cn.sun.pattern.single;

import java.lang.reflect.Constructor;

/**
 * @author : ssk
 * @date : 2020/1/13
 */
public class SingletonTest {

  public static void main(String[] args) {
//    Class<?> singletonClass = Singleton.class;
    Class<?> singletonClass = LazyInnerClassSingleton.class;

    try {

      Constructor<?> constructor = singletonClass.getDeclaredConstructor(null);
      constructor.setAccessible(true);

      Object singleton = constructor.newInstance();

      Object singleton1 = Singleton.getSingleton();

      System.out.println(singleton==singleton1);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}


output:
false

Process finished with exit code 0
  • 防止反射创建
package cn.sun.pattern.single;

/**
 * @author : ssk
 * @date : 2020/1/13
 */
public class LazyInnerClassSingleton {

  private LazyInnerClassSingleton(){
    // ================
    if (LazyHolder.lazy != null) {
      throw new RuntimeException("对象已存在多个实例");
    }
    // ================ 
  }

  public static final LazyInnerClassSingleton getInstance() {
    return LazyHolder.lazy;
  }

  // LazyHolder里面的逻辑需要等到外部方法调用时才执行
  // 巧妙利用了内部类的特性
  // JVM底层底层逻辑,完美避免了线程安全问题
  private static class LazyHolder {

    private static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();

  }

}

2.5 注册式单例(Effective Java)

  • 枚举式单例
package cn.sun.pattern.single;

/**
 * @author : ssk
 * @date : 2020/1/14
 */
public enum  EnumSingleton {

  INSTANCE;

  private Object data;

  public Object getData() {
    return data;
  }
  
  public static EnumSingleton getInstance(){
    return INSTANCE;
  }
}

  • 这种方式可以防止反射和序列化创建的方式
  • 反射说明
    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
                 
        // --------从JDK层面保证了枚举不被反射破坏
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
  • 枚举类型不能通过反射创建实例

  • 序列化说明

    /**
     * Reads in and returns enum constant, or null if enum type is
     * unresolvable.  Sets passHandle to enum constant's assigned handle.
     */
    private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
              // -------------
              // --------------
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }
  • 通过Class对象+枚举量的方式获取枚举实例,Class对象在堆内存中仅有一个,相当于从容器中获取Enum实例。

2.6 容器式单例

package cn.sun.pattern.single;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : ssk
 * @date : 2020/1/14
 */
public class ContainerSingleton {

  private ContainerSingleton() {
  }

  private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

  public static Object getBean(String beanName) {

    synchronized (ioc) {
      if (!ioc.containsKey(beanName)) {
        Object obj = null;
        try {
          obj = Class.forName(beanName).newInstance();
          ioc.put(beanName, obj);
        } catch (Exception e) {
          e.printStackTrace();
        }
        return obj;
      }
    }

    return ioc.get(beanName);
  }

}

3.单例模式的应用

  • 单例模式在Java中的应用

    • Runtime是一个典型的例子,看下JDK API对于这个类的解释
    /**
     * Every Java application has a single instance of class
     * <code>Runtime</code> that allows the application to interface with
     * the environment in which the application is running. The current
     * runtime can be obtained from the <code>getRuntime</code> method.
     * <p>
     * An application cannot create its own instance of this class.
     *
     * @author  unascribed
     * @see     java.lang.Runtime#getRuntime()
     * @since   JDK1.0
     */
    
    每个Java应用程序都有一个单独的Runtime类实例,该实例允许应用与该应用所运行的环境沟通。当前运行时环境可以通过访问getRuntime方法获得。
    一个应用不能创建它自己的Runtime类实例。
    
    • 这段话有两个点比较重要
      • 每个Java应用程序都有一个单独的Runtime类实例
      • 一个应用不能创建它自己的Runtime类实例以及当前运行时环境可以通过访问getRuntime方法获得。
    • Runtime代码如下,可以看到是典型的饿汉式单例实现
    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
    
        /**
         * Returns the runtime object associated with the current Java application.
         * Most of the methods of class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> object associated with the current
         *          Java application.
         */
        public static Runtime getRuntime() {
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {}
      
    }
    
原文地址:https://www.cnblogs.com/riders/p/12193349.html