文章目录
什么是单例模式?
单例模式(Singleton Pattern)是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点以供外部代码使用。在java中表示在jvm中该对象只有一个实例的存在。
单例模式的应用场景
- 1.项目中定义的配置文件
- 2.Servlet对象默认就是单例
- 3.线程池、数据库连接池
- 4.Spring中Bean对象 和 ApplicationContext默认就是单例
- 5.实现网站计数器
- 6.Jvm内置缓存框架
单例优缺点
1.优点
能够节约当前堆内存空间,不需要频繁New对象,能够快速访问
2.缺点
当多个线程访问同一个单例对象的时候可能会存在线程安全问题
单例的七种写法
1.懒汉式(线程不安全)
- 懒汉式基本概念:当真正需要获取到该对象时,才会创建该对象,该写法存在线程安全性问题。
- 懒汉式(懒加载):当我们真正需要该对象时,才会创建该对象。
- 优点:节约内存
- 缺点:使用该单例对象时需要保证,会线程安全性问题
public class Singleton01 {
//当真正需要使用该对象时才会创建。
private static Singleton01 singleton01;
private Singleton01() throws Exception {
// if (singleton01 != null) {
// throw new Exception("该对象已经创建,该类为单例模式");
// }
// System.out.println("无参构造函数");
}
public static Singleton01 getSingleton() throws Exception {
if(singleton01 == null){
singleton01 = new Singleton01();
}
return singleton01;
}
public static void main(String[] args) throws Exception {
Singleton01 singleton1 = Singleton01.getSingleton();
Singleton01 singleton2 = Singleton01.getSingleton();
System.out.println(singleton1 == singleton2);//true
}
}
2.懒汉式(线程安全)
- 在做写操作情况下需要保证线程安全性问题
- 懒汉式在第一次new出该对象已经赋值singleton,后面的所有线程直接获取该singleton对象 不需要重复new
public class Singleton02 {
//实例化的变量引用私有化
private static Singleton02 singleton = null;
/**
* 私有化构造函数
*/
private Singleton02() {
}
// 创建和读取对象都需要获取Singleton02 锁
public static synchronized Singleton02 getSingleton() {
if (singleton == null) {
singleton = new Singleton02();
}
return singleton;
}
public static void main(String[] args) {
Singleton02 singleton1 = Singleton02.getSingleton();
Singleton02 singleton2 = Singleton02.getSingleton();
System.out.println(singleton1 == singleton2);//true
}
}
3.懒汉式(双重检验锁)
- 懒汉式线程安全—双重检验锁
- (DCL,即 double-checked locking)
- 能够保证线程安全,只会创建该单例对象的时候上锁,获取该单例对象不会上锁,效率比较高。
public class Singleton03 {
//注意:加volatile关键字避免重排序
private static volatile Singleton03 singleton03;
private Singleton03(){
}
public static Singleton03 getSingleton(){
if(singleton03 == null){
// 第一个判断new,该单例对象为NULL时才会获取锁
synchronized (Singleton03.class){
if(singleton03 == null){
// 第二个判断是如果之前获取锁的线程创建对象成功,则不会在继续重复创建对象
//new的操作的时候在并发场景下可能会发生重排序(有可能让另外一个线程拿到不完整的对象)
// --- 使用volatile禁止重排序,内存屏障
singleton03 = new Singleton03();
}
}
}
return singleton03;
}
public static void main(String[] args) {
Singleton03 singleton1 = Singleton03.getSingleton();
Singleton03 singleton2 = Singleton03.getSingleton();
System.out.println(singleton1 == singleton2);//true
}
}
4.饿汉式
- 提前创建单例对象
- 优点:先天性线程安全
- 缺点:如果没有使用该对象的话,该对象提前创建会占用堆内存
public class Singleton04 {
//类加载器--当我们的class文件被加载时提前创建该对象,就会提前创建singleton对象
private static Singleton04 singleton04 = new Singleton04();
private Singleton04(){}
public static Singleton04 getSingleton(){
return singleton04;
}
public static void main(String[] args) {
Singleton04 singleton01 = Singleton04.getSingleton();
Singleton04 singleton02 = Singleton04.getSingleton();
System.out.println(singleton01==singleton02);
}
}
5.静态代码块
- 当我们class被加载时,就会提前创建singleton对象
public class Singleton05 {
private static Singleton05 singleton05 = null;
private Singleton05() {
}
static {
//只会执行一次Singleton03
singleton05 = new Singleton05();
}
public static Singleton05 getSingleton() {
return singleton05;
}
public static void main(String[] args) {
Singleton05 singleton01 = Singleton05.getSingleton();
Singleton05 singleton02 = Singleton05.getSingleton();
System.out.println(singleton01 == singleton02);
}
}
6.静态内部类
- 静态内部类实现单例
- 使用静态内部类到达懒汉式效果。也可以先天性保证线程安全的问题
- spring框架源码中经常会发现使用静态内部类单例
public class Singleton06 {
private Singleton06(){
System.out.println("无参构造函数");
}
//当没有调用就静态内部类时,不会提前创建单例对象
private static class SingletonHolder{
private static Singleton06 singleton06 = new Singleton06();
}
public static Singleton06 getSingleton(){
return SingletonHolder.singleton06;
}
public static void main(String[] args) {
Singleton06 singleton01 = Singleton06.getSingleton();
Singleton06 singleton02 = Singleton06.getSingleton();
System.out.println(singleton01 == singleton02);
}
}
7.枚举实现单例
public enum Singleton07 {
TEST,MARK,SIGN;
public void getInstance() {
System.out.println("<<<getInstance>>>");
}
public static void main(String[] args) throws Exception {
Singleton07 test1 = Singleton07.TEST;
Singleton07 test2 = Singleton07.TEST;
System.out.println(test1==test2);//true
Singleton07 test3 = Singleton07.valueOf("TEST");
System.out.println(test1==test3);//true
Singleton07 mark = Singleton07.MARK;
Singleton07 mark2 = Singleton07.MARK;
System.out.println(mark == test1);//false
System.out.println(mark==mark2);//true
//反射攻击枚举
//枚举底层实际上基于类封装的 没有无参构造函数 所有根据无参构造函数反射 会报错
Class<?> c = Class.forName("com.test.Singleton07");
Singleton07 instance3 = (Singleton07) c.newInstance();
System.out.println(test1 == instance3);
//枚举不能够被反射 ,反射底层代码有判断处理
}
}
控制台信息
true
true
false
true
Exception in thread "main" java.lang.InstantiationException: com.test.Singleton07
at java.lang.Class.newInstance(Class.java:427)
at com.test.Singleton07.main(Singleton07.java:27)
Caused by: java.lang.NoSuchMethodException: com.test.Singleton07.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 1 more
如何破解单例模式
一、反射破解单例
反射如何破解单例
public class Singleton01 {
//当真正需要使用该对象时才会创建。
private static Singleton01 singleton01;
// 写单例 特点;在我们当前jvm中只能够存在一个实例
// 对无参构造函数私有化
private Singleton01() throws Exception {
// if (singleton01 != null) {
// throw new Exception("该对象已经创建,该类为单例模式");
// }
// System.out.println("无参构造函数");
}
public static Singleton01 getSingleton() throws Exception {
if(singleton01 == null){
singleton01 = new Singleton01();
}
return singleton01;
}
public static void main(String[] args) throws Exception {
// 使用反射破解单例
Singleton01 singleton01 = Singleton01.getSingleton();
Singleton01 singleton02 = Singleton01.getSingleton();
Class<?> aClass = Class.forName("com.test.Singleton01");
Singleton01 singleton03 = (Singleton01) aClass.newInstance();
System.out.println(singleton02 == singleton03);//false
}
}
如何防止反射单例被破解
将无参构造函数中注解打开
private Singleton01() throws Exception {
if (singleton01 != null) {
throw new Exception("该对象已经创建,该类为单例模式");
}
System.out.println("无参构造函数");
}
控制台信息
Exception in thread "main" java.lang.Exception: 该对象已经创建,该类为单例模式
at com.test.Singleton01.<init>(Singleton01.java:18)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at com.test.Singleton01.main(Singleton01.java:38)
二、序列化破解单例
序列化如何破解单例
private static Singleton08 singleton = new Singleton08();
public static Singleton08 getSingleton() {
return singleton;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton08 singleton1 = Singleton08.getSingleton();
// 1.将对象序列化存入到本地文件中
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton1);
oos.close();
fos.close();
System.out.println("----------从硬盘中反序列化对象到内存中------------");
//2.从硬盘中反序列化对象到内存中
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
// 从新获取一个新的对象
Singleton08 singleton2 = (Singleton08) ois.readObject();
System.out.println(singleton1 == singleton2);
}
}
如何防止序列化单例被破解
- 重写readResolve方法,返回原来对象即可
private Object readResolve() throws ObjectStreamException {
return singleton;
}
参考链接:菜鸟教程 | 单例模式