一 初识
java的单例模式主要有四种实现方式:饿汉式、懒汉式、双重检查锁式、静态内部类。
废话不多说,直接上代码
- 饿汉式
缺点:浪费空间。不管用不用都在类装载时初始化对象。
public class SingletonHungry {
private static final SingletonHungry instance = new SingletonHungry();
/**
* 屏蔽外面直接new实例化对象
*/
private SingletonHungry() {
}
/**
* 饿汉式:
* 类加载时初始化一次(且只初始化一次)对象(static修饰),且instance不能被覆盖(final修饰)
* @return
*/
public static SingletonHungry getInstance(){
return instance;
}
}
- 懒汉式
缺点:加了synchonized关键字,性能上有损耗,在99%的情况下都不存在安全问题。
public class SingletonLazy {
private static SingletonLazy instance = null;
/**
* 屏蔽外面直接new实例化对象
*/
private SingletonLazy(){
};
/**
* 懒汉式:
* static :通过类直接访问。static修饰的成员变量(不能修饰非成员变量)以及静态代码块在类初始化就加载,
* static修饰的方法需要在调用时才加载。这样就能保证对象实例的懒加载。
* synchronized :为了保证线性安全问题必须加锁
* @return
*/
public static synchronized SingletonLazy getInstance(){
if(instance==null){
instance = new SingletonLazy();
}
return instance;
}
}
双重检查锁式:
public class SingletonDoubleCheck {
private static SingletonDoubleCheck instance = null;
/**
* 屏蔽外面直接new实例化对象
*/
private SingletonDoubleCheck(){
};
/**
* 双重检查锁:
* 两次判断instance 是否为null,双重检查,且在最后一次判断之前给后续代码块加锁synchonized
* @return
*/
public static SingletonDoubleCheck getInstance(){
if (instance==null){
synchronized(SingletonDoubleCheck.class){
if(instance==null){
instance = new SingletonDoubleCheck();
}
}
}
return instance;
}
}
- 静态内部类
通过静态内部类可以实现懒加载:静态内部类在调用时才会加载,内存上优于饿汉式。而且没有加锁,性能上优于懒汉式。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
总结
有两个问题需要注意:
1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题(类装载器可以生成多个实例)修复的办法是:
PS:有点像实现类装载器的单例
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
对第二个问题(多次反序列化会复原多个实例对象)修复的办法是:
PS:JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.
public class Singleton implements java.io.Serializable {
private static Singleton INSTANCE = new Singleton();
private Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}
单例模式的关键有两点:
1、构造方法为私有,这样外界就不能随意调用。
2、get实例的方法为静态,由类直接调用
多例模式(Multiton)
1 、多例类可以有多个实例
2 、多例类必须能够自我创建并管理自己的实例,并向外界提供自己的实例。
一、单例模式和多例模式说明:
1. 单例模式和多例模式属于对象模式。
2. 单例模式的对象在整个系统中只有一份,多例模式可以有多个实例。
3. 它们都不对外提供构造方法,即构造方法都为私有。
单例和多例的详细描述:
1. 什么是单例多例:
所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action;
2. 如何产生单例多例:
在通用的SSH中,单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope=”prototype”;
3. 为什么用单例多例:
之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存;
之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一个:
当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例;
4. 何时用单例?何时用多例?
对于struts2来说,action必须用多例,因为action本身含有请求参数的值,即可改变的状态;
而对于STRUTS1来说,action则可用单例,因为请求参数的值是放在actionForm中,而非action中的;
另外要说一下,并不是说service或dao一定是单例,标准同第3点所讲的,就曾见过有的service中也包含了可改变的状态,同时执行方法也依赖该状态,但一样用的单例,这样就会出现隐藏的BUG,而并发的BUG通常很难重现和查找;
5、使用
在Spring中创建的Bean实例默认都是单例模式存在的。如:数据源配置,配置文件,AOP(一般会配置service的事物切入点pointcut),mytbatis配置文件等