特点
- 单例的类在整个JVM中只有一个实例
- 需要提供一个全局访问点(1.公开的静态变量,2.公开的静态方法)
- 对象在内存中只有一个,减少了内存的开销
- 类构造器私有
- 可以控制对象的创建时刻
创建单例的八种方式
饿汉式,懒汉式,枚举
饿汉式
- 类加载到内存后就实例化一个单例,JVM保证线程安全
- 提供一个全局的访问点
缺点
-
1. 饿汉式(1)
能保证单例
public class Mgr01 { // 能保证单例private static final Mgr01 INSTANCE = new Mgr01();private Mgr01() {}public static Mgr01 getInstance() {return INSTANCE;}}
2. 饿汉式(2)
能保证单例
public class Mgr02 { // 能保证单例/*** final修饰的变量必须立即初始化,或者“代码块”初始化* 如果是static修饰的用static代码块初始化* 如果不是static修饰的必须用普通代码块初始化*/private static final Mgr02 INSTANCE;static {INSTANCE = new Mgr02();}private Mgr02() {}public static Mgr02 getInstance() {return INSTANCE;}}
3. 懒汉式(1)
不能保证单例,线程不安全
public class Mgr03 { // 不能保证单例private static Mgr03 INSTANCE;private Mgr03() {}public static Mgr03 getInstance() {if (INSTANCE == null) { // 多线程不安全try {// 模拟线程阻塞Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}INSTANCE = new Mgr03();}return INSTANCE;}}
4. 懒汉式(2)
能保证单例
public class Mgr04 { // 能保证单例private static Mgr04 INSTANCE;private Mgr04() {}/*** 静态方法加锁是:对该类的class对象加锁。Mgr04.class (√)* 加锁会影响性能*/public static synchronized Mgr04 getInstance() {if (INSTANCE == null) {try {// 模拟线程阻塞Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}INSTANCE = new Mgr04();}return INSTANCE;}}
5. 懒汉式(3)
不能保证单例,线程不安全
public class Mgr05 { // 不能保证单例private static Mgr05 INSTANCE;private Mgr05() {}public static Mgr05 getInstance() {if (INSTANCE == null) { // 多线程不安全try {// 模拟线程阻塞Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}synchronized (Mgr05.class) {INSTANCE = new Mgr05();}}return INSTANCE;}}
6. 懒汉式(4)
能保证单例
双重校验方式
public class Mgr06 { // 能保证单例/*** 需要加volatile修饰,禁止指令重排*/private static volatile Mgr06 INSTANCE;private Mgr06() {}public static Mgr06 getInstance() {if (INSTANCE == null) { // 第一次检测,减小上锁的概率try {// 模拟线程阻塞Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}synchronized (Mgr06.class) {if (INSTANCE == null) { // 第二次检测,确保实例一定为空INSTANCE = new Mgr06();}}}return INSTANCE;}}
为什么要加volatile呢?
创建一个对象可以分为3步!
memory = allocate(); //1.分配对象的内存空间ctorInstance(memory); //2.初始化对象instance = memory; //3.设置instance指向刚分配的内存地址其中:2和3之间,可能会被重排序memory = allocate(); //1.分配对象的内存空间instance = memory; //2.设置instance指向刚分配的内存地址ctorInstance(memory); //3.初始化对象
虽然重排序不会影响单线程的执行结果,但是由于判断的条件是instance == null,当分配了内存以后,其他线程来到判断的地方,instance不为空,所以直接将引用指向实例对象。但是实例对象还没有初始化,就会出现问题,可能会引发错误(对象还未初始化就已被使用)。
所以需要加volatile防止重排序。7. 懒汉式(5)
能保证单例
- 静态内部类方式
通过类加载过程的线程安全来保证创建单例实例的线程安全
public class Mgr07 { // 能保证单例private Mgr07() {}public static class InnerMgr07 {private static final Mgr07 INSTANCE = new Mgr07();}public static Mgr07 getInstance() {return InnerMgr07.INSTANCE;}}
8. 枚举方式
唯一安全的单例创建方式(不能够被反序列化) ```java public enum Mgr08 { INSTANCE; }
<a name="T4VTN"></a>#### 枚举在底层是怎么保证线程安全的?> **解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题**- 定义枚举时使用enum和class一样,时Java中的关键字。- 枚举经过javac编译后,会被转成 public final class T extends Enum 的定义。- 枚举中的各个枚举项都是通过static修饰的。```javapublic enum T {SPRING,SUMMER,AUTUMN,WINTER;}
反编译后:
public final class T extends Enum{//省略部分内容public static final T SPRING;public static final T SUMMER;public static final T AUTUMN;public static final T WINTER;private static final T ENUM$VALUES[];static{SPRING = new T("SPRING", 0);SUMMER = new T("SUMMER", 1);AUTUMN = new T("AUTUMN", 2);WINTER = new T("WINTER", 3);ENUM$VALUES = (new T[] {SPRING, SUMMER, AUTUMN, WINTER});}}
- static 类型的属性会在类被加载之后被初始化
- 当一个Java类第一次被真正使用到的时候静态资源被初始化,Java类的加载和初始化过程都是线程安全的
- 因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,而这个方法使用同步代码块保证了线程安全
- 所以,创建一个enum类型是线程安全的
枚举在底层是怎么避免反序列化的?
在序列化的时候,Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject等方法。
普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。
但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题
单例与序列化的爱恨情仇
序列化
- Java序列化可以保存实例属性的状态,但是不会保存方法,因为方法没有状态。
- Java序列化不会保存static变量,因为static变量是类的状态,序列化保存的是对象,而不是类。
- transient关键字修饰的变量也不会被序列化保存,反序列化时,该变量会被设置为默认值。
- 父类实现了Serializable接口,其子类会继承,从而子类不需要显示实现Serializable接口。
- 子类实现了Serializable接口,其父类如未实现该接口,子类序列化不会保存父类属性的状态。
-
单例与序列化
public class Singleton implements Serializable{private static volatile Singleton singleton = null;private String s;private Singleton(){}public static Singleton getInstance(){if (singleton == null){synchronized (Singleton.class){if (singleton == null)singleton = new Singleton();}}return singleton;}public String getS() {return s;}public void setS(String s) {this.s = s;}}
以上代码是一个单例模式的实现,Singleton实现了Serializable接口,并且保证了线程安全。有一个私有属性s,通过set和get方法进行赋值取值操作。下面来看序列化与反序列化:
public class serialzableTest {public static void serialzableSingleton(String file) {// 1.序列化ObjectOutputStream objectOutputStream = null;Singleton instance1 = null;try {objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));instance1 = Singleton.getInstance();instance1.setS("序列化测试!");objectOutputStream.writeObject(instance1);} catch (IOException e) {e.printStackTrace();} finally {if (objectOutputStream != null) {try {objectOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}// 反序列化ObjectInputStream objectInputStream = null;Singleton instance2 = null;try {objectInputStream = new ObjectInputStream(new FileInputStream(file));instance2 = (Singleton) objectInputStream.readObject();System.out.println(instance2.getS());System.out.println(instance2 == instance1);} catch (Exception e) {e.printStackTrace();} finally {if (objectInputStream != null) {try {objectInputStream.close();} catch (IOException e) {e.printStackTrace();}}}}}
为了代码的规范,因此内容稍微有点多,但主要内容其实就是try里面的两段,最重要的就是注释1和2的两句。测试的结果为:This is a serializable test!和false。这说明序列化确实保存了属性的状态,并且反序列化后的对象singleton2和序列化之前的singleton1不是同一个对象,即单例模式被破坏。那么对ObjectInputStream的readObject方法进行跟踪,发现了以下代码:
private Object readOrdinaryObject(boolean unshared)throws IOException{if (bin.readByte() != TC_OBJECT) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);desc.checkDeserialize();Class<?> cl = desc.forClass();if (cl == String.class || cl == Class.class|| cl == ObjectStreamClass.class) {throw new InvalidClassException("invalid class descriptor");}Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null; //1.对象是否可以进行实例化} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}passHandle = handles.assign(unshared ? unsharedMarker : obj);ClassNotFoundException resolveEx = desc.getResolveException();if (resolveEx != null) {handles.markException(passHandle, resolveEx);}if (desc.isExternalizable()) {readExternalData((Externalizable) obj, desc);} else {readSerialData(obj, desc);}handles.finish(passHandle);if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj); //2. 执行readResolve方法if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep); //3. 将需要返回的obj指向rep}}return obj;}
注释1表示如果对象可以在运行时被实例化,则使用反射新建一个实例,否则返回null。因此,序列化也是使用反射新建了一个实例,破坏了单例模式。那么注释2就是避免序列化破坏单例的一种方式。我们只需要在单例的类中实现 readResolve 方法,利用它返回其实例,那么序列化得到的对象就是那个唯一的实例。
private Object readResolve(){return singleton;}
