单例模式

描述:

Singleton(单例)是设计模式的一种,为了保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要特点:

  1. 单例类确保自己只有一个实例(构造函数私有:不被外部实例化,也不被继承)。
  2. 单例类必须自己创建自己的实例。
  3. 单例类必须为其他对象提供唯一的实例。

单例模式的应用:

资源管理器,回收站,打印机资源,线程池,缓存,配置信息类,管理类,控制类,门面类,代理类通常被设计为单例类
如果程序有多个类加载器又同时使用单例模式就有可能多个单例并存就要找相应解决方法了

如下细述各种实现方式的优缺点:

如果应用程序总是创建并使用单例实例或在创建和运行时开销不大。

1
2
3
4
5
6
7
8
9
10
11
12
//饿汉式单例类
public class EagerSingleton {
// jvm保证在任何线程访问uniqueInstance静态变量之前一定先创建了此实例
private static EagerSingleton uniqueInstance = new EagerSingleton();
// 私有的默认构造子,保证外界无法直接实例化
private EagerSingleton() {
}
// 提供全局访问点获取唯一的实例
public static EagerSingleton getInstance() {
return uniqueInstance;
}
}

如果开销比较大,希望用到时才创建就要考虑延迟实例化,或者Singleton的初始化需要某些外部资源(比如网络或存储设备),就要用后面的方法了.

1
2
3
4
5
6
7
8
9
10
11
12
13
懒汉式单例类
public class LazySingleton {
private static LazySingleton uniqueInstance;

private LazySingleton() {
}

public static synchronized LazySingleton getInstance() {
if (uniqueInstance == null)
uniqueInstance = new LazySingleton();
return uniqueInstance;
}
}

同步一个方法可能造成程序执行效率下降100倍,完全没有必要每次调用getInstance都加锁,事实上我们只想保证一次初始化成功,其余的快速返回而已,如果在getInstance频繁使用的地方就要考虑重新优化了.


volatile 的功能:

  1. 避免编译器将变量缓存在寄存器里
  2. 避免编译器调整代码执行的顺序

优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DoubleCheckedLockingSingleton {    
// java中使用双重检查锁定机制,由于Java编译器和JIT的优化的原因系统无法保证我们期望的执行次序。
// 在java5.0修改了内存模型,使用volatile声明的变量可以强制屏蔽编译器和JIT的优化工作
private volatile static DoubleCheckedLockingSingleton uniqueInstance;
private DoubleCheckedLockingSingleton() {

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

Lazy initialization holder class 满足所有 Double-Checked Locking 满足的条件,并且没有显示的同步操作

1
2
3
4
5
6
7
8
9
10
public class LazyInitHolderSingleton {    
private LazyInitHolderSingleton() {
}
private static class SingletonHolder {
private static final LazyInitHolderSingleton INSTANCE = new LazyInitHolderSingleton();
}
public static LazyInitHolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

可通过反射机制攻击“单例模式”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Eivis {
private Eivis(){

}
private static class SingletonHolder{
private static final Eivis INSTANCE=new Eivis();
}
public static Eivis getInstance(){
return SingletonHolder.INSTANCE;
}
public void doSomethingElse(){

}
public static void main(String[] args) throws NoSuchMethodException, SecurityException,
InstantiationException, IllegalAccessException, IllegalArgumentException,
nvocationTargetException {
Class classType=Eivis.class;
Constructor c=classType.getDeclaredConstructor(null);
c.setAccessible(true);
Eivis e1=(Eivis)c.newInstance();
Eivis e2=Eivis.getInstance();
System.out.println(e1==e2);//false
}
}

单例模式被破坏时抛异常

网上的如下预防措施有问题,思考:flag变量是否也可以通过反射机制改变然后能反射调用私有构造器实例化?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Eivis {
private static boolean flag=false;
private Eivis(){
synchronized(Eivis.class){
if(flag==false){
flag=!flag;
}else{
throw new RuntimeException("单例模式被侵犯!");
}
}
}
private static class SingletonHolder{
private static final Eivis INSTANCE=new Eivis();
}
public static Eivis getInstance(){
return SingletonHolder.INSTANCE;
}
public void doSomethingElse(){

}
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class classType=Eivis.class;
Constructor c=classType.getDeclaredConstructor(null);
c.setAccessible(true);
Eivis e1=(Eivis)c.newInstance();
Eivis e2=Eivis.getInstance();
System.out.println(e1==e2);
}
}


单元素枚举类实现单例,实现单例的最佳方法更简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使在复杂的序列化或者反射攻击的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class EnumSingleton{
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private static enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM会保证此方法绝对只调用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
}

缺点:很难隔离测试,通常将“具有单实例行为”的对象当成对一个类的依赖,然后通过依赖注入框架构建单一的对象


补充

实现单例类的可序列化,仅仅实现Serializable是不够的,为了维护并保证单例,必须声明所有实例域都是瞬时(transient)的,并提供一个readResolve方法,否则每次反序列化一个序列化实例时,都会创建一个新的实例。

修复的办法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class User implements Serializable {
private User(){}
private static User getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private User user;
//JVM会保证此方法绝对只调用一次
private Singleton(){
user=new User();
}
private User getInstance(){
return user;
}
}
private Object readResolve(){
return Singleton.INSTANCE.getInstance();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
File file=new File("e:/temp");
if(!file.exists()){
file.createNewFile();
}
User user1=User.getInstance();
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(user1);
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
User user2= (User) ois.readObject();
System.out.println(user1==user2);//true
}
}

如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

修复的办法是:

1
2
3
4
5
6
7
8
9
10
private static Class getClass(String classname)	throws ClassNotFoundException {     

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

if(classLoader == null){
classLoader = Singleton.class.getClassLoader();
}

return (classLoader.loadClass(classname));
}

0%