单例模式
目录
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执行权如何切换,一个线程都能完整的将代码块中的代码执行完。例如线程A执行到17行时丧失执行权,但是DoubleCheckLockSingleton的锁未被释放,其他线程即使获得CPU执行权也无法进入同步代码块,等到线程A再次获得执行权后将代码块中的代码执行完毕,在内存中创建一个DoubleCheckLockSingleton实例后,将锁释放。其他线程再进入同步代码块中,判断条件
- 指令重排的问题:
- CPU执行的时候回转换成JVM指令执行
- 内存分配给这个对象
- 初始化对象
- 将初始化好的对象和内存地址建立关联,赋值
- CPU执行的时候回转换成JVM指令执行
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() {} }