单例模式

概念

单例模式(Singleton Pattern)属于创建型模式。通过单例模式的方法创建的类在一个JVM中只有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

特点

  • 类构造器私有
  • 持有自己类型的私有静态属性
  • 对外提供获取实例的静态方法

示例如下:

public class Singleton {  
    //持有私有静态实例,防止被引用
    private static final Singleton instance = new Singleton();
    //私有构造方法,防止被实例化 
    private Singleton (){}  
    //静态方法,创建实例
    public static Singleton getInstance() {  
        return instance;  
    }  
}

优点:

  • 在内存里只有一个实例,可以节省内存空间,减轻GC压力;
  • 避免频繁的创建和销毁对象,可以提高性能;
  • 避免对资源的多重占用、同时操作。

实现方式

饿汉

上面的示例即是饿汉式。
饿汉式是线程安全的,基于 classloader 机制避免了多线程的同步问题,不用加锁,执行效率高,实现起来比较简单。但是由于在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

懒汉

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
	    if (instance == null) {  
	        instance = new Singleton();  
	    }  
    	return instance;  
    }  
}

懒汉模式虽然可以延迟加载,但是在多线程情况下并不能保证线程安全

加锁

针对线程不安全的懒汉式的单例,可以通过给创建对象的方法加锁来解决。每个线程在进入getInstance()方法前,要先等候别的线程离开该方法,即不会有两个线程可以同时进入此方法执行 new Singleton(),从而保证了单例的有效。

public static synchronized Singleton getInstance() {
	if (instance == null) {
		instance = new Singleton();
	}
	return instance;
}

双重检查锁

上一种方式因为synchronized加锁范围是整个方法,该方法的所有操作都是同步进行的。但事实上,只有在第一次创建对象的时候需要加锁,之后没有必要进入if语句,也根本不需要同步操作,可以直接返回instance,在方法上加锁反倒会造成性能下降。因此考虑将锁由方法改为代码块上,如下:

public static Singleton getInstance() {
	if (instance == null) {   
	    synchronized(Singleton.class){
	        instance = new Singleton();
	    }
	}
	return instance;
}

但是同步代码块并不能做到线程安全,仍可能产生多个实例,需要增加一个单例不为空的判断来确保线程安全,即双重检查方式。

public static Singleton getInstance() {
	if (instance == null) {   
	    synchronized(Singleton.class){
	        if (instance == null) {
	            instance = new Singleton();
	        }
	    }
	}
	return instance;
}

然而,双重检查锁仍然存在隐患。
首先先明确下instance = new Singleton();这句话是怎么执行的,new一个对象需要如下几个步骤:

  1. 判断class对象是否加载,如果没有就先加载class对象;
  2. 分配内存空间;
  3. 调用构造函数,初始化实例;
  4. 返回地址给引用。

而cpu为了优化程序,可能会进行指令重排序,打乱这3,4这两个步骤。

举个栗子,instance刚被分配了内存空间,还没完成new Singleton()的全部操作,其他线程在进行第一次判断instance是否为空的的时候,结果是false(由于instance指向的是个内存地址,所以分配了内存空间之后,instance这个变量已经不为空),这个时候这个线程就会直接返回,然而instance变量指向的内存空间还没完成new Singleton()的全部操作。 这样一来,一个没有被完全初始化的instance就会被使用。

而volatile可以禁止指令重排序,保证多个线程可以正确获取单例实例,改动如下:

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton(){}  
    public static Singleton getSingleton() {  
	    if (singleton == null) {  
	        synchronized (Singleton.class) {  
		        if (singleton == null) {  
		            singleton = new Singleton();  
		        }  
	        }  
	    }  
	    return singleton;  
    }  
}

静态代码块

静态代码块方式事实上是饿汉式的一种变种,由于当类被加载时,静态代码块也会被执行,因此并没有真的解决懒加载的问题。

public class Singleton { 
    private static Singleton instance;
    static {
        instance = new Singleton();
    }
    private Singleton(){
    }
    public static Singleton getInstance(){  
        return instance;  
    }  
} 

静态内部类

静态内部类同样利用了classloder的机制来保证初始化instance时只有一个线程,跟饿汉式不同之处在于饿汉式只要Singleton类被装载,instance就会被实例化,而静态内部类是Singleton类被装载了,instance并不一定被初始化。
因为Inner 类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载Inner类,从而实例化instance。如果考虑延迟加载,可以考虑此方式。

public class Singleton { 
    private Singleton(){
    }
    public static Singleton getInstance(){  
        return Inner.instance;  
    }  
    private static class Inner {  
        private static final Singleton instance = new Singleton();  
    }  
} 

单例&序列化

序列化可能会破坏单例模式,它会通过反射调用无参数的构造方法创建一个新的实例,通过对Singleton的序列化与反序列化得到的对象是一个新的实例。
在反序列化操作时,从流中读取对象的 readObject() 方法之后会创建并返回一个新的实例。如果在 readResolve() 方法中用原来的 instance 替换掉从流中读取到的新创建的 instance,就可以避免使用序列化方式破坏单例了。

// You can prevent this by using readResolve() method, since during serialization readObject() is used to create instance and it return new instance every time but by using readResolve you can replace it with original Singleton instance.  
private Object readResolve() {
   return instance;
}

枚举

为了解决序列化的问题,还可以通过枚举的方式来解决。
Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象,从而保证了枚举实例的唯一性。

public enum Singleton {
    INSTANCE;//枚举变量
    private VariableName variableName;//枚举变量属性
    Singleton(){
        //此处完成属性的赋值
        variableName=.....
    }
    //返回该属性
    public  VariableName getVariableName(){
        return variableName;
    }
}

单例&克隆

通常来讲,单例模式的类是不可以实现 Cloneable 接口的。
但是如果实现了,如何防止通过clone()来创建一个创建单例实例的另一个实例呢?
可以 override 它的 clone() 方法,使其抛出异常来防止。

Preferred way is not to implement Clonnable interface as why should one wants to create clone() of Singleton and if you do just throw Exception from clone() method as  “Can not create clone of Singleton class“。
@Override
public Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

ps:枚举不支持克隆,因为Enum类已经将clone()方法定义为final了。

单例&反射

如何防止通过反射来创建一个创建单例实例的另一个实例呢?
如果借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器创建对象。
要防止此情况发生,可以在私有的构造器中加一个判断,需要创建的对象不存在就创建;存在则抛出异常提示单例已经初始化。

Since constructor of Singleton class is supposed to be private it prevents creating instance of Singleton from outside but Reflection can access private fields and methods, which opens a threat of another instance. This can be avoided by throwing Exception from constructor as “Singleton already initialized”
private Singleton(){
    if(null!=instance){
        throw new RuntimeException("Singleton already initialized");
    }
}

其它

JDK中的单例

java.lang.Runtime

/**
 * 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     #getRuntime()
 * @since   JDK1.0
 */
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() {}
}

Calendar

注意:
Calendar不是单例模式
单例模式要求每次返回的永远是同一个对象,即对象的引用是相同的,而Calendar.getInstance()方法得到的对象是不同的对象。

/**
 * Gets a calendar using the default time zone and locale. The
 * <code>Calendar</code> returned is based on the current time
 * in the default time zone with the default
 * {@link Locale.Category#FORMAT FORMAT} locale.
 *
 * @return a Calendar.
 */
public static Calendar getInstance()
{
    return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}

private static Calendar createCalendar(TimeZone zone,
                                       Locale aLocale)
{
    CalendarProvider provider =
        LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                             .getCalendarProvider();
    if (provider != null) {
        try {
            return provider.getInstance(zone, aLocale);
        } catch (IllegalArgumentException iae) {
            // fall back to the default instantiation
        }
    }

    Calendar cal = null;

    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype != null) {
            switch (caltype) {
            case "buddhist":
            cal = new BuddhistCalendar(zone, aLocale);
                break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone, aLocale);
                break;
            case "gregory":
                cal = new GregorianCalendar(zone, aLocale);
                break;
            }
        }
    }
    if (cal == null) {
        // If no known calendar type is explicitly specified,
        // perform the traditional way to create a Calendar:
        // create a BuddhistCalendar for th_TH locale,
        // a JapaneseImperialCalendar for ja_JP_JP locale, or
        // a GregorianCalendar for any other locales.
        // NOTE: The language, country and variant strings are interned.
        if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
            cal = new BuddhistCalendar(zone, aLocale);
        } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                   && aLocale.getCountry() == "JP") {
            cal = new JapaneseImperialCalendar(zone, aLocale);
        } else {
            cal = new GregorianCalendar(zone, aLocale);
        }
    }
    return cal;
}

单例&垃圾回收

单例对象是否会被jvm回收

在hotspot虚拟机中,垃圾收集算法使用根搜索算法,基本思路就是通过一系列的称为“GC Roots”的对象作为起始点, 从这些节点开始向下搜索, 搜索所走过的路径称为引用链( Reference Chain),当一个对象到 GC Roots 没有任何引用链相连( 用图论的话来 说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

可以作为根的对象有:

  • Local variables 虚拟机栈栈桢中的本地变量表;
  • Static variables 静态属性变量;
  • Active JAVA Threads 活着的线程;
  • JNI References JNI的引用的对象

由于java中单例模式创建的对象被自己类中的静态属性所引用,单例对象不会被jvm垃圾收集。

单例类是否会被JVM卸载

jvm卸载类的判定条件如下:

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例;
  • 加载该类的ClassLoader已经被回收;
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

只有当以上三个条件都满足,jvm才会在垃圾收集的时候卸载类。由于单例实例不会被回收,单例类也就不会被卸载。也就是说,只要单例类中的静态引用指向jvm堆中的单例对象,那么单例类和单例对象都不会被垃圾收集,依据根搜索算法,对象是否会被垃圾收集与未被使用时间长短无关,仅仅在于这个对象是不是“活”的。

单例&spring

singleton && prototype

在spring中scope的默认值为singleton,即单例。表明容器中创建时只存在一个实例,所有引用此bean都是单一实例。
此外,singleton类型的bean定义从容器启动到第一次被请求而实例化开始,只要容器不销毁或退出,该类型的bean的单一实例就会一直存活。
spring的单例默认为饿汉模式,如果将default-lazy-init属性或 lazy-init设置为true,为懒汉模式,在第一个请求时才初始化一个实例,以后的请求都调用这个实例。
Spring 对 Bean 实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是 ConcurrentHashMap 对象。单例注册表的实现可以用于操作一组对象的聚集。
注意,lazy-init的优先级比default-lazy-init要高。

当scope配置为prototype时,则每次都要新创建一个Bean实例。当Spring创建了bean的实例后,bean的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期,包括该对象的销毁。

< beans  default-lazy-init ="true" >
   <bean id="service" type="bean路径" lazy-init="true"/> 
</beans>

/**
 * Return an instance, which may be shared or independent, of the specified bean.
 * @param name the name of the bean to retrieve
 * @param requiredType the required type of the bean to retrieve
 * @param args arguments to use when creating a bean instance using explicit arguments
 * (only applied when creating a new instance as opposed to retrieving an existing one)
 * @param typeCheckOnly whether the instance is obtained for a type check,
 * not for actual use
 * @return an instance of the bean
 * @throws BeansException if the bean could not be created
 */
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
		final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
		throws BeansException {
     // 对 Bean 的 name 进行特殊处理,防止非法字符
	final String beanName = transformedBeanName(name);
	Object bean;

	// Eagerly check singleton cache for manually registered singletons.
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		...
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}

	else {
		// Fail if we're already creating this bean instance:
		// We're assumably within a circular reference.
		...

		try {
			final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			checkMergedBeanDefinition(mbd, beanName, args);

			// Guarantee initialization of beans that the current bean depends on.
			String[] dependsOn = mbd.getDependsOn();
			if (dependsOn != null) {
				for (String dep : dependsOn) {
					if (isDependent(beanName, dep)) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
					}
					registerDependentBean(dep, beanName);
					getBean(dep);
				}
			}

			// Create bean instance.
			if (mbd.isSingleton()) {
				sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
					@Override
					public Object getObject() throws BeansException {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					}
				});
				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
			} else if (mbd.isPrototype()) {
				// It's a prototype -> create a new instance.
				Object prototypeInstance = null;
				try {
					beforePrototypeCreation(beanName);
					prototypeInstance = createBean(beanName, mbd, args);
				}
				finally {
					afterPrototypeCreation(beanName);
				}
				bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
			} else {
				...
			}
		}
		catch (BeansException ex) {
			...
		}
	}

	// Check if required type matches the type of the actual bean instance.
	...
	return (T) bean;
} 

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/**
 * Return the (raw) singleton object registered under the given name.
 * <p>Checks already instantiated singletons and also allows for an early
 * reference to a currently created singleton (resolving a circular reference).
 * @param beanName the name of the bean to look for
 * @param allowEarlyReference whether early references should be created or not
 * @return the registered singleton object, or {@code null} if none found
 */
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

有状态&&无状态

有状态会话bean :每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。简单来说,有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。
无状态会话bean :bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。简单来说,无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。

有状态bean的线程安全

  • 将有状态的bean配置成prototype模式,让每一个线程都创建一个prototype实例。
  • 使用ThreadLocal变量,为每一条线程设置变量副本。

对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,从而隔离了多个线程对数据的访问冲突,因此可以同时访问而互不影响。

spring对事务的处理即是通过threadLocal来保证线程安全的。

public abstract class TransactionSynchronizationManager {

	private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<String>("Current transaction name");

	private static final ThreadLocal<Boolean> currentTransactionReadOnly =
			new NamedThreadLocal<Boolean>("Current transaction read-only status");

	private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
			new NamedThreadLocal<Integer>("Current transaction isolation level");

	private static final ThreadLocal<Boolean> actualTransactionActive =
			new NamedThreadLocal<Boolean>("Actual transaction active");
}

/**
 * Bind the given resource for the given key to the current thread.
 * @param key the key to bind the value to (usually the resource factory)
 * @param value the value to bind (usually the active resource object)
 * @throws IllegalStateException if there is already a value bound to the thread
 * @see ResourceTransactionManager#getResourceFactory()
 */
public static void bindResource(Object key, Object value) throws IllegalStateException {
	Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
	Assert.notNull(value, "Value must not be null");
	Map<Object, Object> map = resources.get();
	// set ThreadLocal Map if none found
	if (map == null) {
		map = new HashMap<Object, Object>();
		resources.set(map);
	}
	Object oldValue = map.put(actualKey, value);
	// Transparently suppress a ResourceHolder that was marked as void...
	if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
		oldValue = null;
	}
	if (oldValue != null) {
		throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
				actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
	}
	if (logger.isTraceEnabled()) {
		logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
				Thread.currentThread().getName() + "]");
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值