单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
public class Singleton1 implements Serializable {
//构造私有(所有的单例模式构造都得私有)
private Singleton1() {
//下面这个if 预防反射破坏单例
/*if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}*/
System.out.println("private Singleton1()");
}
//赋值给静态变量,最终放在静态代码块中执行,静态代码块的线程安全由jvm保证了线程安全;
//想办法将对象的创建放入静态代码块就是线程安全的,即方式5
private static final Singleton1 INSTANCE = new Singleton1();
//提供公共的静态方法获取实例
public static Singleton1 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
/**
* readResolve() 预防 反序列化破坏单例 (固定方法名的)
*/
/*public Object readResolve() {
return INSTANCE;
}*/
}
上面的代码就是要一个简单的饿汉式的单例模式,可以配合TestSingleton.java
来测试单例模式。
private static final Singleton1 INSTANCE = new Singleton1();
赋值给静态变量最终是在静态代码块中执行的,而静态代码块的线程安全由jvm保证了。但是坏处是浪费内存空间 (不管你用不用都创建了)。单例模式可能会被破坏:
Serializable
接口,但是实现了该接口之后可能会破坏单例模式,可以再在TestSingleton.java
来测试。/**
* readResolve() 预防 反序列化破坏单例 (固定方法名的)
*/
public Object readResolve() {
return INSTANCE;
}
//下面这个if 预防反射破坏单例
if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}
/**
* 枚举类实现饿汉模式只需要下面一行即可
public enum Singleton2 {
INSTANCE;
}
enum:从jdk1.5之后提供的一个关键字,用于定义枚举类;
Enum:是一个抽象类,所有使用enum关键字定义的类默认继承了该类Enum 实现了Serializable接口
*/
public enum Singleton2 {
//枚举变量最终也是在静态代码块中创建枚举变量对应实例对象;静态代码块线程安全由jvm保证了线程安全;
INSTANCE;
//以下代码都是为了测试需要 枚举的构造默认就是私有的
private Singleton2() {
System.out.println("private Singleton2()");
}
@Override
public String toString() {
//默认只会打印 INSTANCE
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
/**
* 可以没有,因为枚举变量都是公共的
* @return
*/
public static Singleton2 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
单例模式可能会被破坏:
枚举实现的饿汉模式也不会有线程安全问题;而且非常的简洁;也不会有因为反序列化和反射来破坏单例
public class Singleton3 implements Serializable {
private Singleton3() {
System.out.println("private Singleton3()");
}
private static Singleton3 INSTANCE = null;
public static Singleton3 getInstance() {
// 加载静态方法上的synchronized 就是给 类.class 加锁
//public static synchronized Singleton3 getInstance() {
if (INSTANCE == null) { //有线程安全问题 需要加上 synchronized 关键字即可,虽然能解决问题但是性能不好:只有首次才会有问题,后续就没问题了;最好
//是首次创建单例时提供线程安全的保护,后续就不需要加 synchronized,此时就是 DCL懒汉式
INSTANCE = new Singleton3();
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
当第一次调用getInstance()时才会创建这个实例。但是如果在多线程环境下会有问题,可以在getInstance()方法上添加synchronized
解决;虽然能解决多线程环境下的问题,但是会有性能问题,因为只有在多线程下首次创建单例时才需要提供线程安全保护,创建完成之后就不需要synchronized
了,此时的优化就是DCL懒汉式(DOUBLE CHECK LOCKING 双检锁懒汉式),详见方式4.
public class Singleton4 implements Serializable {
private Singleton4() {
System.out.println("private Singleton4()");
}
/**必须加 volatile 的修饰(解决共享变量的 可见性,有序性)在这里主要是有序性问题,禁止指令重排序
* 需要理解 INSTANCE = new Singleton4(); 底层工作过程
* 原因:
* INSTANCE = new Singleton4(); 这行代码 对应 反编译之后代码如下
* 创建对象,分配内存空间;(计算有哪些成员变量,需要的内存空间)
* 17: new #6 // class com/cls/demo/基础篇/pattern/Singleton4
* 20: dup
* 调用构造方法 指构造方法 (构造方法和创建对象是两步操作) 成员变量的赋值 是在 构造方法中执行的
* 21: invokespecial #7 // Method "":()V
* 给静态变量赋值 在这里是 将创建好的对象赋值给 INSTANCE
* 24: putstatic #5 // Field INSTANCE:Lcom/cls/demo/基础篇/pattern/Singleton4;
* 即三步,创建对象,调用构造,给静态变量赋值
* 但是cpu可能会对指令的执行次序做出优化,如果指令之间没有因果关系(谁先谁后没关系),cpu可能会调换他们的执行次序
* 上述反编译之后的 21,24是可能被调换次序的,在单线程下没有影响,在多线程下可能会有问题
*
*/
private static volatile Singleton4 INSTANCE = null;
public static Singleton4 getInstance() {
/**
* 加锁之前先判断是否为空
*/
if (INSTANCE == null) {
synchronized (Singleton4.class) {
//防止两个线程都进入了上层的if的情况
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
INSTANCE = new Singleton4() 不是一个原子操作,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造,如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null 时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
public class Singleton5 implements Serializable {
private Singleton5() {
System.out.println("private Singleton5()");
}
/**
* 内部类可以访问外部类的私有变量,私有构造
* 是线程安全的
*/
private static class Holder {
static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
public class TestSingleton {
public static void main(String[] args) throws Exception {
//Singleton1 即使提到最前面也会报错?new Singleton1();执行时间 调反射会发生类加载,就会实例化INSTANCE对象
//reflection(Singleton1.class);
//测试是懒汉式还是饿汉式,在调用getInstance()之前,调用另一个静态方法,触发当前这个类的加载链接初始化,就会导致这个单例对象被创建
System.out.println("otherMethod之前。。");
Singleton5.otherMethod();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(Singleton3.getInstance());
System.out.println(Singleton3.getInstance());
// 反射破坏单例 就不是单例了
/**Singleton1 如果移到最上面还是会报错的?反射创建实例必会导致初始化的呀,初始化会把static那里执行完的?
* 先会执行反射创建对象,然后创建完成之后优惠调用 //提供静态的成员变量 private static final Singleton1 INSTANCE = new Singleton1(); 则会报错
*/
//reflection(Singleton3.class);
// 反序列化破坏单例(需要实现Serializable接口)
serializable(Singleton3.getInstance());
// Unsafe(jdk内置不能直接访问,可以反射拿到Unsafe实例) 破坏单例(预防不了)
unsafe(Singleton3.class);
}
private static void unsafe(Class<?> clazz) throws InstantiationException {
Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
System.out.println("Unsafe 创建实例:" + o);
}
private static void serializable(Object instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);//对象变为字节流
//ObjectInputStream 会对枚举类做特殊处理,遇到枚举类会直接将枚举类实例直接返回
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
System.out.println("反序列化创建实例:" + ois.readObject());//字节流还原为对象 会产生新对象而且是不走构造方法的;写了readResolve()就会调用readResolve()返回对象,就不会使用字节数组反序列化的结果了
}
private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
System.out.println("反射。。");
//非枚举时使用
Constructor<?> constructor = clazz.getDeclaredConstructor();
/**枚举是没有无参构造的 所以枚举时调用 getDeclaredConstructor() 会有问题
* 枚举构造是有两个参数的(可以看Sex类)
*/
//枚举时使用 也是会报错的 Cannot reflectively create enum objects
//Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
System.out.println("反射创建实例:" + constructor.newInstance());
//枚举时
//System.out.println("反射创建实例:" + constructor.newInstance("OTHER",1));
}
}
在此例中(1)饿汉、(2)枚举方式以及(5)静态内部类实现的单例实例化都处于类加载时机,所以它们都是线程安全的;(3)不是线程安全的,(4)是线程安全的(DCL)