1.定义
- 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
- 隐藏其所有的构造方法
- 属于创建型模式
2.适用场景
确保任何情况下都绝对只有一个实例
例如:ServletContext、ServletConfig、ApplicationContext、DBPool
3.单例模式的常见写法
3.1 饿汉式单例(在单例类首次加载时就创建实例)
优点
执行效率高,性能高,没有任何的锁
缺点
类加载的时候就初始化,在某些情况下可能会造成内存浪费,因为不管用不用就会被初始化,内存没浪费的是当使用的时候才去初始化,所以说当大量使用的时候,用饿汉式单例会内存浪费
案例代码
/**
* 优点:执行效率高,性能高,没有任何的锁
* 缺点:类加载的时候就初始化,在某些情况下可能会造成内存浪费,
* 因为不管用不用就会被初始化,内存没浪费的是当使用的时候才去初始化
* 所以说当大量使用的时候,用饿汉式单例会内存浪费
*/
public class HungrySingleton {
// static 在类加载的时候就被初始化了
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public HungrySingleton getInstance(){
return hungrySingleton;
}
}
- 静态
public class HungryStaticSingleton {
//类加载顺序
//先静态后动态
//先上,后下
//先属性后方法
private static final HungryStaticSingleton hungryStaticSingleton;
//没什么区别,装B
static {
hungryStaticSingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){
}
public HungryStaticSingleton getInstance(){
return hungryStaticSingleton;
}
}
3.2 懒汉式单例(被外部调用时才创建实例)
优点
优点:节省了内存
缺点
缺点:出现线程不安全,可能出现两个线程去调用这个实例
案例
/**
* 优点:节省了内存
* 缺点:出现线程不安全,可能出现两个线程去调用这个实例
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton ;
// 私有化构造方法
private LazySimpleSingleton(){
}
// 提供一个全局访问点
public synchronized static LazySimpleSingleton getInstance(){
if (lazySimpleSingleton == null){
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}
验证线程不安全
- 一个线程
/**
* 一个线程
*/
public class ExectorThread implements Runnable{
@Override
public void run() {
LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" +lazySimpleSingleton);
}
}
- 测试
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");
}
}
运行结果
- 同一个实例
1.正常顺序执行
2.后者覆盖前者情况 - 不同的实例
同时进入条件,按顺序返回
解决线程不安全——加锁(让一个线程执行完再执行另一个线程)
/**
* 优点:节省了内存
* 缺点:出现线程不安全,可能出现两个线程去调用这个实例
*
* 解决线程不安全——> synchronized加锁
* ——>新的缺点——瓶颈,性能受到影响
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton ;
// 私有化构造方法
private LazySimpleSingleton(){
}
// 提供一个全局访问点
// 加锁解决线程不安全
public synchronized static LazySimpleSingleton getInstance(){
if (lazySimpleSingleton == null){
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}
加锁的影响——新的缺点——瓶颈,性能受到影响
例如高铁站门口检查
双重检查锁解决加锁的缺点
/**
* 双重锁
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅
*/
public class LazyDoubleCheckSingleton {
// 双重检查锁会出现指令重排序的问题,要解决的话加上volatile
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
// 检查是否要阻塞
if (lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
// 检查是否要重新创建实例
if (lazyDoubleCheckSingleton == null){
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
解决双重检查锁的不够优雅的缺点(静态内部类写法)
/**
* 看似饿汉式,实际懒汉式
* 加载——> LazyStaticInnerClassSingleton.class 一开始加载
* LazyStaticInnerClassSingleton$LazyHolder.class 只有到return LazyHolder.INSTANCE;才被加载
*
* 优点:写法优雅,利用Java本身语法特点,性能高,避免了内存浪费
* 缺点:能够被反射破坏1
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
/**
* 静态内部类
*/
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
还原反射破坏单例的场景
/**
* 破坏了单例
*/
public class ReflectTest {
public static void main(String[] args) {
try {
Class<?> clazz = LazyStaticInnerClassSingleton.class;
Constructor<?> c = clazz.getDeclaredConstructor(null);
// 因为构造方法是私有的,这里设置可以忽视私有
c.setAccessible(true);
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
解决反射破坏单例的情景
/**
* 看似饿汉式,实际懒汉式
* 加载——> LazyStaticInnerClassSingleton.class 一开始加载
* LazyStaticInnerClassSingleton$LazyHolder.class 只有到return LazyHolder.INSTANCE;才被加载
*
* 优点:写法优雅,利用Java本身语法特点,性能高,避免了内存浪费,不能够被反射破坏
* 缺点:构造方法加上了就不够优雅
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
if (LazyHolder.INSTANCE != null){
throw new RuntimeException("非法访问");
}
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
/**
* 静态内部类
*/
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
3.3 注册式单例(将每一个实例都缓存到统一的容器中,使用唯一标识符获取实例)
枚举式案例代码
/**
* 非常优雅了
* 但是因为INSTANCE是一开始就声明的,就类似于饿汉式
* 不适于大批量的情况下使用
* 接下来就看SpringIOC的了
*/
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
// 提供一个全局访问点
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
反射测试
/**
*枚举是无法创建实例的
*/
public class EnumSingletonTest {
public static void main(String[] args) {
// EnumSingleton enumSingleton = EnumSingleton.getInstance();
// enumSingleton.setData(new Object());
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
// 因为private所以要setAccessible
c.setAccessible(true);
Object o = c.newInstance();
System.out.println(o);
}catch (Exception e){
e.printStackTrace();
}
}
}
结果
容器式解决大规模生产单例的问题
因为枚举不适于大批量的情况下使用,所以可用容器式解决大规模生产单例,用Spring
public class ContainerSingleton {
private ContainerSingleton(){
}
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className){
Object instance = null;
if (!ioc.containsKey(className)){
try {
instance = Class.forName(className).newInstance();
ioc.put(className,instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else {
return ioc.get(className);
}
}
}
- 测试
public class ContainerSingletonTest {
public static void main(String[] args) {
Object instance1 = ContainerSingleton.getInstance("com.DesignLearn.SingletonPattern.test.pojo");
Object instance2 = ContainerSingleton.getInstance("com.DesignLearn.SingletonPattern.test.pojo");
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
}
还原反序列化破坏单例的场景
-
序列化
把内存中对象的状态转换为字节码的形式
把字节码通过IO输出流,写到磁盘上
永久保存下来,持久化 -
反序列化
把持久化的字节码内容,通过IO输入流读到内存中
转化为一个Java对象 -
案例代码
public class SeriableSingleton implements Serializable {
// 序列化
// 把内存中对象的状态转换为字节码的形式
// 把字节码通过IO输出流,写到磁盘上
// 永久保存下来,持久化
// 反序列化
// 把持久化的字节码内容,通过IO输入流读到内存中
// 转化为一个Java对象
private static SeriableSingleton seriableSingleton = new SeriableSingleton();
private SeriableSingleton(){
}
public static SeriableSingleton getInstance(){
return seriableSingleton;
}
}
- 测试
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try{
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 =(SeriableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}catch (Exception e){
e.printStackTrace();
}
}
}
-
结果
结果应该要一样的 -
解决方法
public class SeriableSingleton implements Serializable {
// 序列化
// 把内存中对象的状态转换为字节码的形式
// 把字节码通过IO输出流,写到磁盘上
// 永久保存下来,持久化
// 反序列化
// 把持久化的字节码内容,通过IO输入流读到内存中
// 转化为一个Java对象
private static SeriableSingleton seriableSingleton = new SeriableSingleton();
private SeriableSingleton(){
}
public static SeriableSingleton getInstance(){
return seriableSingleton;
}
// 解决放序列化问题
private Object readResolve(){
return seriableSingleton;
}
}
3.4 ThreadLocal单例(保证线程内部的全局唯一,且天生线程安全)
案例代码
- ThreadLocalSingleton
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){
}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
- 测试ThreadLocalSingletonTest
public class ThreadLocalSingletonTest {
public static void main(String[] args) {
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}
}
- 测试ExectorThread
/**
* 一个线程
*/
public class ExectorThread implements Runnable{
@Override
public void run() {
ThreadLocalSingleton threadLocalSingleton = ThreadLocalSingleton.getInstance();
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(Thread.currentThread().getName() + ":" +threadLocalSingleton);
}
}
4.总结
单例模式的优点
- 在内存中只有一个实例
- 可以避免对资源的多重利用
- 设置全局访问点,严格控制访问
单例模式的缺点
- 没有接口,扩展困难
- 如果要扩展单例对象,只有修改代码,没有其他途径
重点总结
- 私有化构造器
- 保证线程安全
- 延迟加载
- 防止序列化和反序列化破坏单例
- 防御反射攻击单例