1.饿汉式
//饿汉式,可能会浪费内存空间
class Hungry {
private final static Hungry INSTANCE = new Hungry();
public final static Hungry getInstance() {
return INSTANCE;
}
private Hungry() {
}
}
2.懒汉式
1.双重检测锁(DCL)
//懒汉式,DLC双重检测锁。在反射下不安全
class Lazy{
private static Lazy INSTANCE;
public static Lazy getInstance(){
if (INSTANCE == null){//如果不为null,就直接返回INSTANCE,不用在进行加锁操作
synchronized (Lazy.class){
if (INSTANCE == null){
INSTANCE = new Lazy();
}
}
}
return INSTANCE;
}
private Lazy(){}
}
2.在反射下DLC不安全。反射可以直接获取到类的私有构造函数,从而创建对象。
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Lazy lazy = constructor.newInstance(null);
System.out.println(lazy);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}).start();
}
}
执行结果:
com.shbj.thread.signal.Lazy@90472a2
com.shbj.thread.signal.Lazy@4388eabf
com.shbj.thread.signal.Lazy@1e057600
com.shbj.thread.signal.Lazy@626213bf
com.shbj.thread.signal.Lazy@616831d4
3.三重检测锁,防止多次创建对象。反射下也不安全,只能防止先调用了getInstance()方法后,使用反射创建对象。
//懒汉式,三重重检测锁。在反射下也不安全
class Lazy2 {
private static Lazy2 INSTANCE;
public static Lazy2 getInstance() {
if (INSTANCE == null) {//如果不为null,就直接返回INSTANCE,不用在进行加锁操作
synchronized (Lazy2.class) {
if (INSTANCE == null) {
INSTANCE = new Lazy2();
}
}
}
return INSTANCE;
}
private Lazy2() {
if (INSTANCE != null) {
throw new RuntimeException("禁止使用反射,破坏单例。");
}
}
}
执行结果:
com.shbj.thread.signal.Lazy2@1b6d3586
java.lang.reflect.InvocationTargetException
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 com.shbj.thread.signal.Demo1.lambda$main$0(Demo1.java:15)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.RuntimeException: 禁止使用反射,破坏单例。
at com.shbj.thread.signal.Lazy2.<init>(Demo1.java:94)
... 6 more
4.采用红绿灯,防止多次创建对象。
//懒汉式。在反射下也不安全
class Lazy2 {
private static Lazy2 INSTANCE;
public static Lazy2 getInstance() {
if (INSTANCE == null) {//如果不为null,就直接返回INSTANCE,不用在进行加锁操作
synchronized (Lazy2.class) {
if (INSTANCE == null) {
INSTANCE = new Lazy2();
}
}
}
return INSTANCE;
}
private Lazy2() {
if (INSTANCE == null) {
synchronized (Lazy2.class) {
if (INSTANCE == null) {
INSTANCE = this;
} else {
throw new RuntimeException("禁止使用反射,破坏单例。");
}
}
} else {
throw new RuntimeException("禁止使用反射,破坏单例。");
}
}
}
//如果知道标记名,也可通过反射修改标记,使得可以创建多个对象。
public static void main(String[] args) {
Lazy2 lazy2 = Lazy2.getInstance();
System.out.println(lazy2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
Constructor<Lazy2> constructor = Lazy2.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Field instance = Lazy2.class.getDeclaredField("INSTANCE");
instance.setAccessible(true);
instance.set(null, null);
Lazy2 lazy = constructor.newInstance(null);
System.out.println(lazy);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}).start();
}
}
执行结果:
com.shbj.thread.signal.Lazy2@1b6d3586
com.shbj.thread.signal.Lazy2@4d4f0cbe
com.shbj.thread.signal.Lazy2@1e057600
com.shbj.thread.signal.Lazy2@4533731f
com.shbj.thread.signal.Lazy2@616831d4
java.lang.reflect.InvocationTargetException
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 com.shbj.thread.signal.Demo1.lambda$main$0(Demo1.java:20)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.RuntimeException: 禁止使用反射,破坏单例。
at com.shbj.thread.signal.Lazy2.<init>(Demo1.java:109)
... 6 more
Process finished with exit code 0
3.静态内部类模式
由于内部类只有在被引用的时候才会加载进内存,所以只有在调用了getInstance()方法时,Inner才会被加载,INSTANCE才会被初始化,这样在内部类被加载前就不会浪费内存空间。反射下也不安全。
缺点:不能在创建实例时传入参数,没有懒汉式灵活。
class Outer {
private Outer() {
}
private static class Inner {
private static final Outer INSTANCE = new Outer();
}
public static final Outer getInstance() {
return Inner.INSTANCE;
}
}
4.枚举
可以防止反射创建对象。
enum Signal{
INSTANCE;
public static Signal getInstance(){
return INSTANCE;
}
}
由于反射通过newInstance()创建对象时,会判断要创建的对象类型是否为枚举类型,是枚举就会抛出异常。
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");