单例简介
* 特点:
* 至多有一个实例;
* 提供全局访问点;
* 是创建型设计模式;
* 应用:
* J2EE 标准中,ServletContext、ServletContextConfig;
* spring 框架应用中 ApplicationContext, 数据库的连接池;
饿汉式
简介
* 特点:
* 在类加载时创建单例对象;
* 类加载时间:
* 同一包下第一次调用;
* 非同一包下第一次import;
* 绝对线程安全;
* 类加载机制是jvm提供的, 线程安全;
* 类第一次加载时, 其他使用该类的线程处于阻塞状态, 直到类加载完成, 该类可被使用;
* 优点:
* 开发简单,线程安全;
* 不加锁执行效率高;
* 缺点:
* 类加载时就初始化, 可能浪费内存空间;
* 所以当需要单例的对象较多时, 不适用饿汉式;
* 类加载顺序:
* 先静态,后动态;
* 先属性, 后方法;
* 先上后下
CODE
思路
饿汉式实现:
1.静态成员变量方式;
2.静态代码块方式;
CODE
public class HungrydSingleton {
private HungrydSingleton(){
}
//静态变量
public final static HungrydSingleton singletonV1 = new HungrydSingleton();
public static HungrydSingleton getInstanceV1(){
return singletonV1;
}
//静态代码块
public static HungrydSingleton singletonV2 = null;
static{
singletonV2 = new HungrydSingleton();
}
public static HungrydSingleton getInstanceV2(){
return singletonV2;
}
}
懒汉式
简介
* 特点:
* 被调用的时, 才会创建单例对象;
* 在未被调用的情况下, 比饿汉式节约内存, 但是不被调用创建还有什么意义呢?
代码
思路
* 饿汉式实现方式有三种:
1.同步代码块;
2.doubleCheck
3.静态内部类;
code
public class LazySingleton {
private LazySingleton(){
}
//基本实现, 存在线程安全问题;
public static LazySingleton singletonV1;
public static LazySingleton getInstanceV1(){
if(singletonV1 == null){
singletonV1 = new LazySingleton();
}
return singletonV1;
}
//同步代码块, 解决线程安全问题, 效率较低;
public static LazySingleton singletonV2;
public synchronized static LazySingleton getInstanceV2(){
if(singletonV2 == null){
singletonV2 = new LazySingleton();
}
return singletonV2;
}
//doubleCheck 线程安全, 效率高;
public static LazySingleton singletonV3;
public static LazySingleton getInstanceV3(){
if(singletonV3 == null){
synchronized (LazySingleton.class) {
if(singletonV3 == null){
singletonV3 = new LazySingleton();
}
}
}
return singletonV3;
}
//静态内部类, 当调用时加载, 且内部类采用懒汉式创建对象线程安全;
static class innerClass {
public static final LazySingleton singletonV4 = new LazySingleton();
}
public static LazySingleton getInstanceV4(){
return innerClass.singletonV4;
}
}
注册式
简介
* 特点:
* 把需要单例的实例存放在容器中(在容器中注册);
* 应用:
* ThreadLocal:
* 采用注册式单例模式, 单挑线程中对象是单例的;
* 容器: CurrentHashMap<String,Object>
* key : 线程名;
* Object: 单例对象;
* 枚举本身防单例破坏: 见单例破坏部分;
代码
思路
* 注册时单例最典型的应用是枚举:
* 枚举本身不对外提供构造方法;
* 枚举线程安全;
* 原因: 见下文单例破坏部分;
* 使用其他容器, 需要考虑校验非空时, 线程安全问题;
CODE
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
单例破坏及防止;
简介
1. 反射技术破坏
2. 序列化技术破坏
* Constructor类调用构造器时, 抛异常;
* 存在单例对象时, 调用构造器抛异常;
* 枚举:
* 枚举本身支持防止反射破坏的功能;
* 阅读Constracter中newInstance()源码可发现, 如果class类型是枚举, 那么直接抛出异常, 说明枚举不能使用构造器创建实例;
* 添加readResolve()方法, 方法返回单例对象;
* 枚举本身具备防止序列化破坏的功能;
* 原理:
* 阅读ObjectInputStream中readObject()源码:
* ObjectInputStream为checkResolve(Object obj)根据数据类型的不同, 做了多次重载;
* 其中包括: String/Array/Class/Exception/Enum/Object;
* 一般对象: checkResolve(readOrdinaryObject(unshared));
1. 读取序列化字节并创建对象obj;
2. 判断是否有readResolve()方法, 如果有, 则obj指向readResolve()返回的对象;
3. 返回obj;
* 枚举: checkResolve(readEnum(unshared));
1. 读取序列化字节并创建对象obj;
2. Enum.valueOf(clazz, name); 获取枚举已经存在的单例对象;
* 如果获取到, 则obj指向该枚举的单例对象;
* 如果没获取到, 则直接抛异常;
3.返回obj;
代码
public class BreakSingleton implements Serializable{
private static final long serialVersionUID = 1L;
//防止反射破坏
private BreakSingleton(){
if(singleton != null){
throw new RuntimeException("singleton can not create by constract");
}
//不允许构造器创建;
StackTraceElement stack[] = Thread.currentThread().getStackTrace();
for (StackTraceElement ste:stack){
if(ste.getClassName().equals("java.lang.reflect.Constructor")){
throw new RuntimeException("singleton can not create by constract!");
}
}
}
//防止序列化破坏
private Object readResolve() {
return singleton;
}
private static BreakSingleton singleton;
public static BreakSingleton getInstance(){
if(singleton == null){
synchronized (BreakSingleton.class) {
if(singleton == null){
singleton = new BreakSingleton();
}
}
}
return singleton;
}
}
public static void main(String[] args) throws Exception {
Class<?> clazz = BreakSingleton.class;
Constructor<?> c = clazz.getDeclaredConstructor();
c.setAccessible(true);
BreakSingleton bs = (BreakSingleton) c.newInstance();
BreakSingleton singleton = BreakSingleton.getInstance();
String filename = "d:\\abc";
FileOutputStream fos = new FileOutputStream(filename);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fis);
BreakSingleton singletonV2 = (BreakSingleton) ois.readObject();
ois.close();
System.out.println(singleton == singletonV2);
System.out.println(bs == singleton);
}