Spring中常见的设计模式——单例模式
⼀、单例模式的应⽤场景
单例模式(singleton Pattern)是指确保⼀个类在任何情况下都绝对只有⼀个实例,并提供⼀个全局访问点。J2EE中的ServletContext,ServletContextConfig等;Spring中的ApplicationContext、数据库连接池等。
⼆、饿汉式单例模式
饿汉式单例模式在类加载的时候就⽴即初始化,并且创建单例对象。它是绝对的线程安全、在线程还没出现以前就实现了,不可能存在访问安全问题。
优点:没有增加任何锁,执⾏效率⾼,⽤户体验⽐懒汉式好。
缺点:类加载的时候就初始化了,⽤不⽤都进⾏,浪费内存。
Spring 中IoC容器ApplocationContext本⾝就是典型的饿汉式单例模式:
public class HungrySingleton {
private static final HungrySingleton h = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return h;
}
}
饿汉式单例模式适⽤于单例对象较少的情况。
三、懒汉式单例模式
被外部调⽤才会加载:
public class LazySimpleSingleton {
private LazySimpleSingleton() {
}
private static LazySimpleSingleton lazy = null;
public static LazySimpleSingleton getInstance() {
if (lazy == null) {
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
利⽤线程创建实例:
java中常用的设计模式有哪些
public class ExectorThread implements Runnable {
@Override
public void run() {
LazySimpleSingleton simpleSingleton = Instance();
System.out.println(Thread.currentThread().getName() + ":" + simpleSingleton);
}
}
客户端代码:
public class LazySimpleSingletonTest {
public static void main(String[] args) {
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("END");
}
}
结果:
END
Thread-1:singleton.Lazy.LazySimpleSingleton@298c37fd
Thread-0:singleton.Lazy.LazySimpleSingleton@6ebc1cfd
可以看到产⽣的两个实例的内存地址不同说明产⽣了两个实例,⼤家可以通过以下打断点的⽅式实现不同Thread运⾏状态见进⾏切换。
要解决线程问题第⼀反应是加 synchronized 加在创建实例的地⽅:public static synchronized LazySimpleSingleton getInstance(),但当线程数量较多时,⽤Synchronized加锁,会使⼤量线程阻塞,就需要更好的解决办法:
public static LazySimpleSingleton getInstance() {
if (lazy == null) {
synchronized (LazySimpleSingleton.class) {
if (lazy == null) {
lazy = new LazySimpleSingleton();
}
}
}
return lazy;
}
synchronized (lock) lock这个对象就是 “锁”,当两个并⾏的线程a,b,当a先进⼊同步块,即a先拿到lock对象,这时候a就相当于⽤⼀把锁把synchronized⾥⾯的代码锁住了,现在只有a才能执⾏这块代码,⽽b就只能等待a⽤完了lock对象锁之后才能进⼊同步块。但是⽤
到 synchronized 总归是要上锁的,对性能还是有影响,那就⽤这种⽅式:⽤内部类的⽅式进⾏懒加载。
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {
}
private static final LazyInnerClassSingleton getIngestance() {
return LazyHolder.LAZY;
}
private static class LazyHolder {
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
内部类在LazyInnerClassSingleton类加载时加载,解决了饿汉式的性能问题,LazyInnerClassSingleton在内部类加载
时,getIngestance()⽅法被调⽤之前实例化,解决了线程不安全问题。
四、反射破坏单例
public class LazyInnerClassSingletonTest {
public static void main(String[] args) {
try {
Class<?> clazz = LazyInnerClassSingleton.class;
//通过反射回去私有构造⽅法
Constructor constructor = DeclaredConstructor(null);
//强制访问
constructor.setAccessible(true);
//暴⼒初始化
Object o1 = wInstance();
//创建两个实例
Object o2 = wInstance();
System.out.println("o1:" + o1);
System.out.println("o2:" + o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
o1:singleton.Lazy.LazyInnerClassSingleton@1b6d3586
o2:singleton.Lazy.LazyInnerClassSingleton@4554617c
创建了两个实例,违反了单例,现在在构造⽅法中做⼀些限制,使得多次重复创建时,抛出异常:
private LazyInnerClassSingleton() {
if (LazyHolder.class != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
这应该就是最好的单例了,哈哈哈。
五、注册式单例模式
注册式单例模式⼜称为登记式单例模式,就是将每个实例都登记到某个地⽅,使⽤唯⼀标识获取实例。注册式单例模式有两种:枚举式单例模式、容器式单例模式。注册式单例模式主要解决通过反序列化破坏单例模式的情况。
1.枚举式单例模式
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void steData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
测试代码:
public class EnumSingletonTest {
public static void main(String[] args) {
try {
EnumSingleton instance1 = Instance();
EnumSingleton instance2 = null;
instance1.steData(new Object());
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance2 = (EnumSingleton) adObject();
ois.close();
System.out.Data());
System.out.Data());
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
java.lang.Object@568db2f2
java.lang.Object@568db2f2
那枚举式单例是如何解决反序列化得问题呢?
通过反编译,可以在EnumSingleton.jad⽂件中发现static{} 代码块,枚举式单例模式在静态代码块中给INSTANCE进⾏了赋值,是饿汉式单例模式的实现。查看JDK源码可知,枚举类型其实通过类名和类对象到⼀个唯⼀的枚举对象。因此,枚举对象不可能被类加载器加载多次。
当你试图⽤反射破坏单例时,会报 Cannot reflectively create enum objects ,即不能⽤反射来创建枚举类型。进⼊Customer的newInstance(),其中有判断:如果修饰符是Modifier.ENUM,则直接抛出异常。JDK枚举的语法特殊性及反射也为美剧保驾护航,让枚举式单例模式成为⼀种⽐较优雅的实现。
2.容器式单例
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className) {
synchronized (ioc) {
if (!ainsKey(className)) {
Object o = null;
try {
o = Class.forName(className).newInstance(); ioc.put(className, o);
} catch (Exception e) {
e.printStackTrace();
}
return o;
} else {
(className);
}
}
}
}
spring中使⽤的就是容器式单例模式。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论