一.描述
Singleton(单例)是设计模式的一种,为了保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要用途是保证某个很占系统资源的类,在同一时间只能拥有一个的情况。
例如:一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;
一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
二.单例模式的主要特点:
1.某个类只能有一个实例;
2.必须自行创建这个实例;
3.必须自行向整个系统提供这个实例。
三.简单的具体实现:
第一种:懒汉式 (线程不安全)
public class SingletonDemo {
private static SingletonDemo single = null;
//私有的构造方法,无法主动实例化这个类
private SingletonDemo(){}
//静态工厂方法 通过getInstance得到具体的单例对象
public static SingletonDemo getInstance(){
if(single == null){
single = new SingletonDemo();
}
return single;
}
}
第二种 懒汉 线程安全
package com.hjh.Singleton;
/**
*
* @author Administrator
* 懒汉式单例 线程安全
* 把构造方法private 无法主动实例化Singletondemo类
* 通过getInstance 实例化对象
*/
public class SingletonDemo {
private static SingletonDemo single = null;
private SingletonDemo(){}
//静态工厂方法
public static synchronized SingletonDemo getInstance(){
if(single == null){
single = new SingletonDemo();
}
return single;
}
}
四. 反射破坏单例
看似单例好像私有了构造方法。无法访问,只能通过getInstance()得到唯一的实例对象。实际上java的反射技术可以破坏单例
public class ReflectSingleton {
private static ReflectSingleton singleton = null;
private ReflectSingleton(){};
private String name = "singleton";
/**
* 懒汉式线程安全 单例
*/
public static synchronized ReflectSingleton getInstance(){
//常量写在前面可以避免一些小错误 比如 == 写成 =
if(null == singleton){
singleton = new ReflectSingleton();
}
return singleton;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 利用反射破坏单例
*/
public static void main(String[] args) {
try {
//得到构造方法
Constructor cons = ReflectSingleton.class.getDeclaredConstructor();
//把构造方法设为可访问 private失效
cons.setAccessible(true);
//利用反射实例化的单例对象
ReflectSingleton s1 = (ReflectSingleton) cons.newInstance();
s1.setName("s1");
ReflectSingleton s2 = (ReflectSingleton) cons.newInstance();
s2.setName("s2");
//常规方法实例化的对象
ReflectSingleton s3 = ReflectSingleton.getInstance();
s3.setName("s3");
ReflectSingleton s4 = ReflectSingleton.getInstance();
s4.setName("s4");
System.out.println("s1的name = "+s1.getName() );
System.out.println("s2的name = "+s2.getName() );
System.out.println("s3的name = "+s3.getName() );
System.out.println("s4的name = "+s4.getName() );
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
最后运行结果s3的name = s4 说明s3和s4是同一个单例,
但是s1,s2却成功的创建了新的实例化对象。
这就破坏了单例模式
所以我们必须做出防范。
五.防止反射破坏的单例模式。
package com.hjh.Reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 防止反射破坏的安全单例
*/
public class SafeSingleton {
private String name = "singleton";
private static boolean flag = false;
private SafeSingleton(){
synchronized (this){
//如果没有创建,就创建实例
if(false == flag){
flag = true;
}else{
throw new RuntimeException("单例对象已经创建,不能反复创建");
}
}
};
//static 在 jvm 创建时就会调用该实例化方法创建单例
private static class SingletonHolder{
private static final SafeSingleton INSTANCE = new SafeSingleton();
}
public static synchronized SafeSingleton getInstance(){
//返回这个实例化对象
return SingletonHolder.INSTANCE;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 利用反射破坏单例
*/
public static void main(String[] args) {
try {
//得到构造方法
Constructor cons = SafeSingleton.class.getDeclaredConstructor();
//把构造方法设为可访问 private失效
cons.setAccessible(true);
//利用反射实例化的单例对象
SafeSingleton s1 = (SafeSingleton) cons.newInstance();
s1.setName("s1");
//常规方法实例化的对象
SafeSingleton s2 = SafeSingleton.getInstance();
s2.setName("s2");
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
结果是抛出异常 此时既然s1已经存在,就无法getInstance 或newInstance 得到s2
六. 进阶 --- enum枚举完美实现单例模式
jdk1.5以后的单例模式 里面enum实现
创建一个只有一个实例的enum 不仅可以防止反射破坏,还可以防止序列化破坏单例
写法也非常简单
public enum SingletonClass
{
INSTANCE;
public void test()
{
System.out.println("The Test!");
}
}
单元素的枚举类型已经成为实现Singleton模式的最佳方法。