保证一个类只有唯一的一个实例,并提供一个全局的访问点。
白话文:1个构造方法 + 1个引用(不一定有实例)+ 1个获取单例的方法
饿汉在类加载的时候就会初始化,所以不会有线程安全的问题,getInstance不需要有任何操作,直接拿到instance就行
public class SingletonDemo {
// 在类加载的时候直接new这个实例
private static SingletonDemo instance = new SingletonDemo();
private SingletonDemo(){}
public static SingletonDemo getInstance(){
return instance;
}
}
在类加载的时候直接new这个实例
public class SingletonDemo {
private static SingletonDemo instance = null;
static{
instance = new SingletonDemo();
}
private SingletonDemo(){}
public static SingletonDemo getInstance(){
return instance;
}
}
静态代码块在类加载的时候就会执行
题外话:静态代码块相比直接new的方式,到底好在哪里?TODO
public enum SingletonDemo {
INSTANCE;
public SingletonDemo getInstance(){
return INSTANCE;
}
}
public final class SingletonDemo extends Enum<SingletonDemo> {
public static final SingletonDemo SINGLETONDEMO;
public static SingletonDemo[] values();
public static SingletonDemo valueOf(String s);
static {};
}
// 这是Enum类
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
// 名称
private final String name;
public final String name() {
return name;
}
// 序号
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
}
源码分析:Class类通过反射调用Constructor类的newInstance方法创建实例
public final class Constructor<T> extends Executable {
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 原因在这里:如果这个Class类是属于Enum的话,则会报异常,创建失败
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
}
如果这个Class类是属于Enum的话,则会报异常,创建失败
反序列化创建实例的本质是调用Object的readObject方法,而Enum类的方法一调用就会报异常
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
// 原因在这里:直接报“不能序列化枚举”异常
throw new InvalidObjectException("can't deserialize enum");
}
}
原因是:Enum重写了Object的readObject方法,当调用的时候会直接报异常
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
protected final Object clone() throws CloneNotSupportedException {
// 原因在这里:直接报“不支持Clone”异常
throw new CloneNotSupportedException();
}
}
原因是:Enum重写了Object的clone方法,当调用的时候会直接报异常
在调用静态方法getInstance时会实例化,
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInsatance(){
// 第一次校验,如果已经创建好实例的话,就不用去获取锁了
if (instance == null) {
// A、B两个线程同时到这里来了,A获取了锁,B在这里阻塞等待
synchronized (SingletonDemo.class) {
// 第二次校验,防止未创建实例时,A获取锁创建了实例,B之后获取锁又创建了实例
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return singletonDemo;
}
}
注意:这里用到了双重校验机制,2个IF分别有什么作用要记住,一个都不能删掉
public class SingletonDemo {
// 静态内部类
private static class SingletonHolder{
private static final SingletonDemo instance = new SingletonDemo();
}
private SingletonDemo(){}
public static final SingletonDemo getInsatance(){
return SingletonHolder.instance;
}
}
注意:静态内部类在类加载的时候,是不会被扫描JVM到的,所以不会在类加载的时候实例化
spring的BeanDefinition(因为BeanDefinition)通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式
public class ContainerSingleton {
private ContainerSingleton() {
}
// ioc容器本质就是一个ConcurrentHashMap(确认过了,是的)
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
// 获取实例的方法,不同的是需要有入参
public static Object getInstance(String className) {
Object instance = null;
// 第一次校验
if (!ioc.containsKey(className)) {
synchronized (ContainerSingleton.class) {
// 第二次校验
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
}
}
return ioc.get(className);
}
}
本质是采用在静态方法中使用双重校验实现,区别是存放实例的地方变了
题外话:为什么BeanFactory和ApplicationContext都是调用BeanDefinition来初始化实例的,BeanDefinition使用懒加载的方式实现,BeanFactory可以理解,但是ApplicationContext是如何在Spring容器启动的时候去创建实例的?TODO
原文链接:为什么要用枚举实现单例模式(避免反射、序列化问题)
解决:修改构造方法,当调用的时候直接抛异常就行
public class SerSingleton implements Serializable {
private volatile static SerSingleton uniqueInstance;
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
private SerSingleton() {
}
public static SerSingleton getInstance() {
if (uniqueInstance == null) {
synchronized (SerSingleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new SerSingleton();
}
}
}
return uniqueInstance;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerSingleton s = SerSingleton.getInstance();
s.setContent("单例序列化");
System.out.println("序列化前读取其中的内容:"+s.getContent());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
oos.writeObject(s);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SerSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
SerSingleton s1 = (SerSingleton)ois.readObject();
ois.close();
System.out.println(s+"\n"+s1);
System.out.println("序列化后读取其中的内容:"+s1.getContent());
System.out.println("序列化前后两个是否同一个:"+(s==s1));
}
}
控制台:
序列化前读取其中的内容:单例序列化
com.lxp.pattern.singleton.SerSingleton@135fbaa4
com.lxp.pattern.singleton.SerSingleton@58372a00
序列化后读取其中的内容:单例序列化
序列化前后两个是否同一个:false
任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve
因为readObject方法的存在,导致每次序列化前后的对象
解决:重写readObject方法并在方法内直接抛异常
解决:继承clone并在方法内直接抛异常
问题来源是在这里,答案是我自己总结的
原文链接:https://blog.csdn.net/androidzhaoxiaogang/article/details/6832364
这里它们将检查面试者是否有对使用单例模式有足够的使用经验。他是否熟悉单例模式的优点和缺点。
使用双重校验或静态内部类
很多面试者都在这里失败。然而如果不能编写出这个代码,那么后续的很多问题都不能被提及。
只锁创建实例的那块代码就行,大多数情况下都是获取已经创建的实例
这确实是一个非常好的问题,我几乎每次都会提该问题,用于检查面试者是否会考虑由于锁定带来的性能开销。因为锁定仅仅在创建实例时才有意义,然后其他时候实例仅仅是只读访问的,因此只同步必要的块的性能更优,并且是更好的选择。
问的就是对类加载和性能开销的理解(这个问题在【八股文】JVM篇的时候再讲)
这是和Java中类加载的载入和性能开销的理解的又一个非常好的问题。我面试过的大部分面试者对此并不熟悉,但是最好理解这个概念。
Runtime类封装了Java运行时的环境。每一个java程序实际上都是启动了一个JVM进程,那么每个JVM进程都是对应这一个Runtime实例,此实例是由JVM为其实例化的。每个Java应用程序都有一个 Runtime类实例,使应用程序能够与其运行的环境相连接。
由于Java是单进程的,所以,在一个JVM中,Runtime的实例应该只有一个。所以应该使用单例来实现。
package java.lang;
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
Runtime是通过饿汉模式的new方式创建实例的(实现的方法有点老了)
Runtime很重要(封装了Java运行时的环境),让JWM去实例化,总不可能让JAVA应用程序去实例化吧
这三个类都是在JDK的GUI包下的,并不常使用,所以为了节省资源,使用了懒汉模式
这是个完全开放的问题,如果你了解JDK中的单例类,请共享给我。
说的就是DCL双重校验机制,在懒汉模式下的
单例类去实现Object中的clone方法并直接抛出异常就可以了
问:枚举类默认能防止clone创建实例吗?可以的,Enum类重写了clone方法
该类型问题有时候会通过如何破坏单例或什么时候Java中的单例模式不是单例来被问及。
在私有的构造方法中抛出异常
开放的问题。在我的理解中,从构造方法中抛出异常可能是一个选项。
实现Object的readObject方法并抛出异常
又一个非常好的问题,这需要Java中的序列化知识并需要理解如何使用它来序列化单例类。该问题是开放问题。
问的其实就是什么情况会导致多个实例,如未加锁的并发访问、反射调用构造方法、通过序列化的readObject创建实例、通过Object的clone方法(浅拷贝或深拷贝)创建实例等等(没等等了好像就这么多吧)