1.单例模式的介绍
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
2.单例模式的两种基本实现
- 饿汉式: 饿汉式单例模式在类初始化时就加载了单例对象,本身就是线程安全的。但是当加载类的时间很长,但是后面又没有用这个示例时,就造成了资源浪费。饿汉式示例代码如下。
package singleton;
/**
* 测试饿汉式单例模式
* 加载类时创建对象线程安全 调用效率高
* @author XinghaiLiao
*/
public class SingletonDemo01 {
//类初始化时,立即加载这个对象 后面可能根本没有用 造成资源浪费
private static SingletonDemo01 instance = new SingletonDemo01();
private SingletonDemo01() {}
//方法不用考虑线程同步问题 调用效率高
public static SingletonDemo01 getInstance() {
return instance;
}
}
- 懒汉式: 真正用到单例对象时才加载,但是当多个线程去调用同一个对象的getInstance时会有并发问题。比如线程A调用getInstance的时候发现instance是null,然后进入挂起状态;之后线程B调用getInstance方法new了一个对象,这时线程A接着运行时就又建立了一个新的对象,从而违背类单例模式的初衷。所以懒汉式的单例模式在getInstance方法前面加上synchronized关键字来解决线程同步问题,带来的问题也就是调用效率降低了。懒汉式的示例代码如下。
package singleton;
/**
*@author XinghaiLiao
*懒汉式 延时加载
*/
public class SingletonDemo02 {
private static SingletonDemo02 instance;
private SingletonDemo02() {
}
/**
* 真正用的时候才加载 但是当多个线程去调用的时候会有并发问题 比如A调用的时候发现instance是null
* 然后进入了挂起状态 B调用的时候new了一个新对象 这时候就会有两个单例对象
* @return
*/
public static synchronized SingletonDemo02 getInstance() {
if(instance == null) {
instance = new SingletonDemo02();
}
return instance;
}
}
3.其他方法实现单例模式
- 双重检测锁来实现。由于编译器优化原因和JVM底层内部模型原因,偶尔会出现问题。不建议使用。 但是也给大家介绍一下。这种模式将同步内容放到if内部,只有当instance为空时不同线程访问时才需要同步,当已经有单例实例时则直接获取单例实例。demo代码如下。
package singleton;
/**
1. @author XinghaiLiao
*/
public class SingletonDemo03 {
private static SingletonDemo03 instance;
private SingletonDemo03() {}
public static SingletonDemo03 getInstance() {
if(instance == null) {
SingletonDemo03 s;
synchronized (SingletonDemo03.class) {
s = instance;
if(s == null) {
synchronized (SingletonDemo03.class) {
if(s == null) {
s = new SingletonDemo03();
}
}
instance = s;
}
}
}
return instance;
}
}
- 静态内部类来实现优化。这种方法兼备了线程安全、调用效率高和延迟加载等优点。这种方法添加了一个私有静态内部类,由于初始化这个类时并不会初始化静态内部类,只有调用内部类时才会初始化内部类,从而实现延时加载,而且类加载时线程是安全的。demo代码如下。
package singleton;
/**
1. 静态内部类的实现
2. @author XinghaiLiao
*/
public class SingletonDemo04 {
private static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
private SingletonDemo04() {}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.instance;
}
}
- 使用枚举实现单例模式。优点是实现简单,枚举类本身就是单例模式,可以避免通过反射和反序列化来创建对象。demo代码如下。这种方法调用效率也比较高,遗憾的是不能实现延时加载。demo代码如下。
package singleton;
/**
1. 枚举实现单例模式
2. @author XinghaiLiao
*/
public enum SingletonDemo05 {
//这个枚举元素本身就是单例对象
INSTANCE;
/**
* 添加自己的方法
*/
public void singletonOperation() {
}
}
4.总结
- 这篇是本人学习笔记,有错误的地方希望大家指正(^_−)☆。
- 大家可以写个简单的测试程序测试一下单例模式,比如测试demo。
package singleton;
public class Demo {
public static void main(String[] args) {
SingletonDemo01 s1 = SingletonDemo01.getInstance();
SingletonDemo01 s2 = SingletonDemo01.getInstance();
System.out.println(s1 == s2);
}
}
- 关于暴力反射破解单例模式的测试代码如下,可以发现暴力反射创建的对象和原来的不同。
package reflection;
import singleton.SingletonDemo01;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
1. 暴力反射获取实例
2. @author XinghaiLiao
*/
public class Demo {
public static void main(String[] args) {
SingletonDemo01 s1 = SingletonDemo01.getInstance();
SingletonDemo01 s2 = SingletonDemo01.getInstance();
System.out.println(s1);
System.out.println(s2);
try {
Class<SingletonDemo01> singletonDemo01Class = (Class<SingletonDemo01>)Class.forName("singleton.SingletonDemo01");
Constructor<SingletonDemo01> constructor = singletonDemo01Class.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingletonDemo01 s3 = constructor.newInstance();
System.out.println(s3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
- 如果想要防止用暴力反射的方式来创建对象,可以在构造函数中抛出异常来防止。示例代码如下。这时候如果通过反射获取实例时会直接抛出异常。
package singleton;
/**
4. @author XinghaiLiao
*/
public class SingletonDemo01 {
private static SingletonDemo01 instance = new SingletonDemo01();
private SingletonDemo01() {
if(instance != null) {
throw new RuntimeException();
}
}
public static SingletonDemo01 getInstance() {
return instance;
}
}
- 如果想要具体比较每种方式的效率,可以通过下面的demo代码来测试(下面是测试饿汉式的效率)。
package reflection;
import singleton.SingletonDemo01;
import java.util.concurrent.CountDownLatch;
/**
* 测试效率
* @author XinghaiLiao
*/
public class Demo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
int threadCnt = 10;
CountDownLatch countDownLatch = new CountDownLatch(threadCnt);
for(int i = 0; i < threadCnt; i++) {
new Thread(() -> {
for(int j = 0; j < 1000000; j++) {
Object o = SingletonDemo01.getInstance();
}
countDownLatch.countDown();
}).start();
}
try {
//main线程阻塞,直到计数器清0
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end-start) + "ms");
}
}