饿汉式
一般饿汉式加载所导致的弊端是
- 可能我并不想使用实例但是实例已经被构造,相对于懒汉式的用则构造会造成内存的浪费,但是其实现方式很简单
//饿汉式单例
public class Hungry {
//如此操作可能会浪费空间
private byte[] data1 =new byte[1024*1024];
private byte[] data2 =new byte[1024*1024];
private byte[] data3 =new byte[1024*1024];
private byte[] data4 =new byte[1024*1024];
private Hungry(){
}
//上来就new一个对象 来填填肚子 这就是饿汉
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
public static void main(String[] args) {
Hungry instance = Hungry.getInstance();
System.out.println(instance);
}
}
运行结果:
有一种单例叫做静态内部类单例
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
接下来是懒汉式:
- 懒汉式的单例模式,所谓懒汉式就是延迟加载,什么时候需要什么时候构造。但是在多线程下是不安全的,由于cpu的重排操作导致的,我们需要加锁来使线程安全,还要保证他的原子性操作。
线程的问题解决了,但是还会有反射的问题,java中可以使用反射得到一个类的对象,并且还可以获得并修改私有域!
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class LazyMan {
private LazyMan(){
}
private volatile static LazyMan lazyMan; //保证原子性 不被重排序
//DCL懒汉式
private static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//通过反射破坏私有域
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
System.out.println(instance1);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//通过反射破坏 创建一个对象
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance2);
}
}
但是呢,他可能就没有使用过getInstance()方法,一直使用反射在创建对象,我们可以用过一些信息加密下构造器。如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class LazyMan {
private static boolean james = false; //通过加密 防止反射破坏
private LazyMan(){
if(james == false){
james = true;
}
else{
throw new RuntimeException("不要试图用反射!");
}
}
private volatile static LazyMan lazyMan; //保证原子性 不被重排序
//DCL懒汉式
private static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//通过反射破坏私有域
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//通过反射破坏
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
james.set(instance2,false);
LazyMan instance3 = declaredConstructor.newInstance();
System.out.println(instance3);
}
}
道高一尺魔高一丈,反射是很强大的。
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getInstance();
//虽然有加密,但是如果破解后,我还是可以通过反射来破坏
Field james = LazyMan.class.getDeclaredField("james");
james.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
//通过反射破坏
LazyMan instance2 = declaredConstructor.newInstance();
// 修改加密信息,实现破坏
System.out.println(instance2);
james.set(instance2,false);
LazyMan instance3 = declaredConstructor.newInstance();
System.out.println(instance3);
}
最后我们来看看枚举类是怎么处理反射的?
public class Test {
public static void main(String[] args) throws Exception {
Constructor<EunmSingle> declaredConstructor =
EunmSingle.class.getDeclaredConstructor(null);//无参的
declaredConstructor.setAccessible(true);
EunmSingle eunmSingle = declaredConstructor.newInstance();
System.out.println(eunmSingle);
}
}
报错说没有这个无参构造器。但是源码里确实是无参的,那说明源码骗了我们。我们来反编译下看看。
如图我们得知,源码真的骗了我们,他是有参的构造器,对我们隐瞒了,我改了之后,你们看:
public class Test {
public static void main(String[] args) throws Exception {
Constructor<EunmSingle> declaredConstructor =
EunmSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EunmSingle eunmSingle = declaredConstructor.newInstance();
System.out.println(eunmSingle);
}
}
这是我想要告诉你们的结果,枚举类是不允许通过反射创建的。