《java大法好》之 单例模式
·今天给大家分享一下23种设计模式中的单例模式及如何破解单例模式
什么是单例模式呢,顾名思义,单例模式就是在一个程序中某一个对象只存在且最多存在一个实例
而单例模式的实现方式又可以细分为 懒汉式、饿汉式、双重检测锁模式、静态内部类模式和枚举式,下面来给大家详细介绍一下
单例模式介绍
·懒汉式
优点:线程安全,效率高
缺点:不能延时加载
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Singleton01{
Logger logger = LoggerFactory.getLogger(this.getClass());
private static final Singleton01 instance = new Singleton01();
private Singleton01() {}
public static Singleton01 getInstance() {
return instance;
}
public void doSomething(){
logger.info("hello,[{}]", instance);
}
public static void main(String[] args) {
Singleton01 singleton01 = Singleton01.getInstance();
Singleton01 singleton02 = Singleton01.getInstance();
singleton01.doSomething();
singleton02.doSomething();
}
}
输出结果:
打印结果为同一对象,懒汉式测试通过
·饿汉式
优点:线程安全,可以延迟加载
缺点:效率不高
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Singleton02 {
Logger logger = LoggerFactory.getLogger(this.getClass());
private static Singleton02 instance;
private Singleton02() {}
public synchronized static Singleton02 getInstance() {
if(instance == null){
instance = new Singleton02();
}
return instance;
}
public void doSomething(){
logger.info("hello,[{}]", instance);
}
public static void main(String[] args) {
Singleton02 singleton01 = Singleton02.getInstance();
Singleton02 singleton02 = Singleton02.getInstance();
singleton01.doSomething();
singleton02.doSomething();
}
}
输出结果:
打印结果为同一对象,饿汉式测试通过
· 双重检测锁模式 (由于jvm底层模型原因,可能会出问题,不建议使用)
缺点:由于jvm底层模型原因,可能会出问题
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Singleton03 {
Logger logger = LoggerFactory.getLogger(this.getClass());
private static Singleton03 instance;
private Singleton03() {}
public static Singleton03 getInstance() {
if(instance == null){
Singleton03 singleton03;
synchronized (Singleton03.class){
singleton03 = instance;
if(singleton03 == null){
synchronized (Singleton03.class){
if(singleton03 == null){
singleton03 = new Singleton03();
}
}
instance = singleton03;
}
}
}
return instance;
}
public void doSomething(){
logger.info("hello,[{}]", instance);
}
public static void main(String[] args) {
Singleton03 singleton01 = Singleton03.getInstance();
Singleton03 singleton02 = Singleton03.getInstance();
singleton01.doSomething();
singleton02.doSomething();
}
}
输出结果:
打印结果为同一对象,双重检测锁模式测试通过(不推荐使用这种方式实现)
·静态内部类模式
优点:线程安全,效率高,可以延时加载
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Singleton04 {
Logger logger = LoggerFactory.getLogger(this.getClass());
private static class SingletonInstance {
private static final Singleton04 instance = new Singleton04();
}
public static Singleton04 getInstance(){
return SingletonInstance.instance;
}
private Singleton04(){}
public void doSomething(){
logger.info("hello,[{}]", SingletonInstance.instance);
}
public static void main(String[] args) {
Singleton04 singleton01 = Singleton04.getInstance();
Singleton04 singleton02 = Singleton04.getInstance();
singleton01.doSomething();
singleton02.doSomething();
}
}
输出结果:
打印结果为同一对象,静态内部类模式测试通过
·静态内部类模式
优点:线程安全,效率高,可以延时加载
缺点:不能延时加载
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public enum Singleton05 {
INSTANCE;
Logger logger = LoggerFactory.getLogger(this.getClass());
public void doSomething() {
logger.info("hello,[{}]", INSTANCE);
}
public static void main(String[] args) {
Singleton05 singleton01 = Singleton05.INSTANCE;
Singleton05 singleton02 = Singleton05.INSTANCE;
singleton01.doSomething();
singleton02.doSomething();
}
}
输出结果:
枚举类型由JVM优化,天然单例。枚举方式测试通过
单例模式的破解
·使用反射破解单例模式
话不多说直接上代码
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
public class Client02 {
static Logger logger = LoggerFactory.getLogger(Client02.class);
public static void main(String[] args) throws Exception {
// 使用正常方式获取一个实例
Singleton01 singleton01_1 = Singleton01.getInstance();
// 使用正常方式再获取一个实例
Singleton01 singleton01_2 = Singleton01.getInstance();
logger.info("Hello:{}", singleton01_1);
logger.info("Hello:{}", singleton01_2);
// 使用反射获取一个新的实例
reflectDemo();
}
public static void reflectDemo() throws Exception {
// 通过反射加载类
Class<Singleton01> clazz = (Class<Singleton01>) Class.forName("pattern23.singleton.Singleton01");
// 获取类的无参构造方法(单例模式中一般将构造方法声明为private)
Constructor<Singleton01> constructors = clazz.getDeclaredConstructor();
// 打开私有方法的执行权限
constructors.setAccessible(true);
// 使用构造方法获取新的实例
Singleton01 singleton01_3 = constructors.newInstance();
logger.info("Hello:{}", singleton01_3);
}
}
执行结果:
从控制台输出信息可以看到,前两次的打印都是同一个实例,而第三次打印就是一个新的实例。此时单例模式便被破解了
那么如何防止单例模式被破解呢,很简单,只需要在构造方法里判断一次实例是否为null即可,上代码:
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Singleton01 {
Logger logger = LoggerFactory.getLogger(this.getClass());
private static final Singleton01 instance = new Singleton01();
private Singleton01() {
// 在构造方法里判断一下实例是否为null
// 如果为null则正常执行
// 如果不为null则说明是有人想使用反射来破解我们的单例模式
if (instance != null) {
throw new RuntimeException("禁止通过反射创建多个实例");
}
}
public static Singleton01 getInstance() {
return instance;
}
public void doSomething() {
logger.info("hello,[{}]", instance);
}
}
好了,下面我们来测试一下,来看一下修改了代码之后的执行结果:
可以看见,前两次正常获取的实例皆为同一个对象,而第三次我们使用反射想要获取一个新的实例时就抛出了异常。自此,防止使用反射破解单例模式便改造完成了。
·使用反序列化解单例模式
除了使用反射破解单例模式外还可以使用反序列化来破解单例模式,下面给大家演示一下如何使用反序列化来破解单例模式:
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
public class Client02 {
static Logger logger = LoggerFactory.getLogger(Client02.class);
public static void main(String[] args) throws Exception {
Singleton06 singleton06_1 = Singleton06.getInstance();
logger.info("Hello:{}", singleton06_1);
File file = new File("SingletonDemoFile.temp");
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(singleton06_1);
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Singleton06 singleton06_2 = (Singleton06) objectInputStream.readObject();
logger.info("Hello:{}", singleton06_2);
Singleton06 singleton06_3 = Singleton06.getInstance();
logger.info("Hello:{}", singleton06_3);
}
}
下面我们执行一下这段代码,控制台打印如下:
可以看见第一次打印和第三次打印的都是同一个实例,而第二次打印的则是一个新的实例。怎么肥事,单例模式又被破解了......
那么我们应该怎么防止怪蜀黍用反序列化的方式来破解单例模式呢,这是一个值得思考的问题,爱折腾的小编在Serializable接口中发现了这样一句话
可以在readResolve()方法中使用任意对象来替换反序列化得到的对象,好像有了一些思路....
下面上代码:
package pattern23.singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Singleton06 implements Serializable {
Logger logger = LoggerFactory.getLogger(this.getClass());
private static final Singleton06 instance = new Singleton06();
public static Singleton06 getInstance() {
return instance;
}
public void doSomething() {
logger.info("hello,[{}]", instance);
}
// 这里直接返回当前实例对象
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
我们再来测试一下改造之后的代码:
这次我们可以看见,三次打印的都是同一个实例,自此,使用反序列化破解单例模式这条路也被我们堵住了。
小白程序员学习分享,有可以改进的地方还希望大家多多提出建议共同成长