单例模式
主要:构造器私有 (别人无法new 这个对象,保证内存中只有咱这一个对象)
饿汉模式
主要:上来就 new 出来这个对象
类加载的方式是按需加载,且只加载一次。 因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说, 饿汉式单例天生就是线程安全的 。
缺点:有可能浪费内存。
/**
* 饿汉式单例
*/
public class Hungry {
//可能会浪费空间,因为上来就加载
private byte[] b1= new byte[1024*1024];
private byte[] b2= new byte[1024*1024];
private byte[] b3= new byte[1024*1024];
private byte[] b4= new byte[1024*1024];
//构造器私有化(别人无法new 这个对象,保证内存中只有咱这一个对象)
private Hungry(){
}
//上来就new 出来这个对象
private static final Hungry HUNGRY = new Hungry();
//抛出一个外部可获取对象的方法
public static Hungry getInstance(){
return HUNGRY;
}
}
DCL懒汉模式
/**
* 懒汉式单例
*/
public class LazyMan {
//私有化构造器
private LazyMan(){
System.out.println(Thread.currentThread().getName() +" "+LazyMan.class);
}
//定义对象 ,等待用时再加载 [volatile:避免指令重排]
private volatile static LazyMan LAZY_MAN;
//定义对象 ,等待用时再加载
// private static LazyMan LAZY_MAN;
//创建外部访问的方法,创建/获取对象 并返回
public static LazyMan getInstance(){
//多线程并发问题应多一层判断【双重检测锁模式 的懒汉式单例 。简称DCL懒汉式】
if(LAZY_MAN == null){
//先上把锁, 锁这个 LazyMan 这个对象 【注:锁class 的话只有一个】
synchronized (LazyMan.class){//在锁里面在判断一次
//对象值为空 证明初次获取对象 ,此时应创建一个对象返回
if(LAZY_MAN == null){
LAZY_MAN = new LazyMan();//不是原子性操作
/**
* 不是原子性那么执行了哪些操作呢
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间
* 这样才能能保证他把这个对象 new 完了
*
* 有可能会出现指令重排
* 比如: 我们期待的是 123
* 最终执行时 132 那么 就不会走 LAZY_MAN == null 这行代码 直接return 了,
* 此时lazyMan还没有完成构造,空间是一片虚无的。
* 所以必须保证 lazyMan 不能指令重排【volatile】
*/
}
}
}
return LAZY_MAN;
}
//单线程下 单例ok ,当多线程并发时 会出现问题
public static void main(String[] args) {
//测试多线程下
for (int i = 0; i < 10; i++) {
new Thread( () ->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类创建单例
/**
* 静态内部类
*/
public class Holder {
//首先私有化构造器
private Holder(){
}
//创建外部调用方法
public static Holder getInstance(){
//直接返回内部类中的 HOLDER
return InnerClass.HOLDER;
}
//创建静态内部类
public static class InnerClass{
//创建实例
private static final Holder HOLDER = new Holder();
}
}
反射破解
懒汉模式
1. 反射代码
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
//获取这个对象
Class<LazyMan> lazyManClazz = LazyMan.class;
//获取 空参 构造器
Constructor<LazyMan> declaredConstructor = lazyManClazz.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1 );
System.out.println(instance2 );
}
- 由于 (1)中 instance1 是通过 LazyMan.getInstance();获取的
那么可以在懒汉模式中通过加锁来防止
//私有化构造器
private LazyMan(){
synchronized (LazyMan.class){
//判断 LazyMan 是否已经存在了 被创建过了
if (LAZY_MAN != null){
throw new RuntimeException("不要试图通过反射破坏单例异常");
}
}
}
- 如果反射被 (2) 中的方法拦截,反射可做相应的调节【两个对象都通过反射来创建】
public static void main(String[] args) throws Exception {
// LazyMan instance1 = LazyMan.getInstance();
//获取这个对象
Class<LazyMan> lazyManClazz = LazyMan.class;
//获取 空参 构造器
Constructor<LazyMan> declaredConstructor = lazyManClazz.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有
//两个对象都通过反射来创建
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1 );
System.out.println(instance2 );
}
- 如果 反射如 (3)中逻辑书写 , 单例也可对应修改
//定义一个非当前对象[xxx 可为密钥等一些复杂点的 加密处理等]
private static boolean xxx = false;
//私有化构造器
private LazyMan(){
synchronized (LazyMan.class){
if(xxx == false){
xxx = true;
}else{
throw new RuntimeException("不要试图通过反射破坏单例异常");
}
}
}
5. 如果 单例如 (4)中逻辑书写 , 反射也可对应修改
public static void main(String[] args) throws Exception {
// LazyMan instance1 = LazyMan.getInstance();
//获取这个对象
Class<LazyMan> lazyManClazz = LazyMan.class;
//获取变量
Field xxxField = lazyManClazz.getDeclaredField("xxx");
xxxField.setAccessible(true);
//获取 空参 构造器
Constructor<LazyMan> declaredConstructor = lazyManClazz.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有
LazyMan instance1 = declaredConstructor.newInstance();
xxxField.set(instance1,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1 );
System.out.println(instance2 );
}
。。。。
魔高一尺,道高一丈!
单例不安全,反射可破解。
枚举不能被破坏,他没有无参构造,他有个有参构造器 还是两个参数。