什么是单例模式?
单例模式(Singleton Pattern)是Java中最简单的设计模式之一。
它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
饿汉式单例模式
这个模式在类被加载的时候就会实例化一个对象
public class Hungry{
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
饿汉式是最简单的单例模式的写法,保证了线程的安全。
优点:简单快速
缺点:直接初始化,消耗性能
普通懒汉式
public class LazyMan{
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
但是在并发的环境下,单例会失效,所以需要加锁!
DCL懒汉式
public class LazyMan{
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized(LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
优点:保证了线程的安全性,符合懒加载,只有在用到的时候,才会去初始化,效率也较高
但是这个单例模式是有一定的问题的:
问题
lazyMan = new LazyMan();
这个语句不是原子性操作,至少经过三个步骤:
1、分配对象内存空间
2、执行构造方法初始化对象
3、设置instance指向刚分配的内存空间,此时instance!=null
由于指令重排,当A线程执行lazyMan = new LazyMan();
时,可能先执行了第三步(没有执行第二步),此时线程B进来了,发现lazyMan
不为空,直接返回了,并且后面使用了返回的lazyMan
,由于线程A都还没有初始化对象,导致现在的lazyMan
不完整,可能会有意想不到的错误,所以就需要用到volatile关键字来避免指令重排:
DCL+volatile单例模式
public class LazyMan{
private LazyMan(){
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized(LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
静态内部类饿汉式(改进)
public class Holder{
private Holder(){
}
public static Holder getInstance(){
return InnerClass.holder;
}
private static class InnerClass{
private static final Holder holder = new Holder();
}
}
优点:保证了线程安全性,同时满足了懒加载
反射对单例模式的破坏
反射可以无视private修饰的构造方法,可以直接在外面创建多个单例对象,把我们的单例模式破坏掉
public static void main(String[] args) {
try {
LazyMan lazyMan1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1==lazyMan2);
} catch (Exception e) {
e.printStackTrace();
}
}
可以看到我们的测试结果,创建的两个lazyMan是不相等的
解决方案
在构造方法中加一个class锁,判断如果lazyMan不为空,则直接抛出异常
public class LazyMan{
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要试图破坏我的单例模式!");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized(LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
还有问题!
如果一开始就用反射创建两个对象,还是可以创建两个不同的对象!
public static void main(String[] args) {
try {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1==lazyMan2);
} catch (Exception e) {
e.printStackTrace();
}
}
继续解决方案!
定义一个boolean变量flag,初始值为false,私有构造函数里面做了一个判断,如果flag==false,就改为true;如果flag等于true,就抛出异常
public class LazyMan{
private static boolean flag = false;
private LazyMan(){
synchronized (LazyMan.class){
if(flag==false){
flag = true;
}else{
throw new RuntimeException("不要破坏我的单例模式!");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized(LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
但是还是不能完全防止反射破坏单例模式,因为可以利用反射修改flag的值!
public static void main(String[] args) {
try {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
Field field = LazyMan.class.getDeclaredField("flag");
field.setAccessible(true);
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
field.set(lazyMan1,false);
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1==lazyMan2);
} catch (Exception e) {
e.printStackTrace();
}
}
所以我们需要用到枚举!
枚举
public enum enumSingleton {
INSTANCE;
public enumSingleton getInstance(){
return INSTANCE;
}
}
public class demo02 {
public static void main(String[] args) {
enumSingleton singleton1 = enumSingleton.INSTANCE;
enumSingleton singleton2 = enumSingleton.INSTANCE;
System.out.println(singleton1==singleton2);
}
}
创建的类是一样的!
如果我们使用反射去破坏,首先需要知道,枚举在IDEA中的源码
public enum enumSingleton {
INSTANCE;
private enumSingleton() {
}
public enumSingleton getInstance() {
return INSTANCE;
}
}
显示是无参构造,但其实如果我们使用jad进行反编译可以看到枚举的构造方法是有参数的!
private enumSingleton(String s,int i){
super(s,i);
}
所以开始破坏!
public static void main(String[] args) {
try {
Constructor<enumSingleton> declaredConstructor = enumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
enumSingleton singleton2 = declaredConstructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
不能破坏,显示不能反射创建!
ss.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
enumSingleton singleton2 = declaredConstructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
不能破坏,显示不能反射创建!
本文章是通过观看bilibili狂神说视频整理出来的!狂神老师讲的很好,希望大家可以去看看!