目录
一、单例模式
1. 饿汉式
2. 懒汉式
3. 静态内部类实现
总结:
4. 反射:
但是极限情况下有可能标志位被破解,找到标志位之后也进行反射,修改权限
编辑
5. 枚举类型:
一、单例模式
1. 饿汉式
每个类只初始化一个对象。这个对象,无论你需要不需要,我都会给你,因此有可能会造成内存空间浪费。也因此引出懒汉式单例模式
package singleton;
public class Hungry {
//饿汉式单例模式
//可能会浪费空间,我们想要在使用的时候再申请内存空间,因此引入懒汉式
private byte[] data1 = new byte[1024*1024];
Private byte[] data2 = new byte[1024*1024];
private Hungry(){ //构造方法私有化
}
private final static Hungry Hungry = new Hungry();
public static Hungry getInstance(){
return Hungry;
}
}
2. 懒汉式
每个类只初始化一个对象,起初这个对象只是声明,在你需要的时候才会:
1. 分配内存空间
2. 执行构造方法,初始化对象
3. 对象指向内存空间
遇到的问题:
多线程下并发问题:双重锁机制
原子性问题: 添加volatile关键字防止指令重排
package singleton;
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + " oK!");
}
//DCL + volatile——》 双重检测锁 + 原子性操作
//双重检测 锁 模式的懒汉式单例——》DCL
//DCL也会出现问题,因为LazyMan = new LazyMna();的时候并不是原子操作,会出现指令重排的现象
//因此需要进一步改进,添加volatile关键字保证原子性操作
public static volatile LazyMan LazyMan;
public static LazyMan getInstance(){
if(LazyMan == null){
synchronized (LazyMan.class) {
if (LazyMan == null) {
LazyMan = new LazyMan(); //不是原子性操作
//1. 分配内存空间
//2. 执行构造方法,初始化对象
//3. 将对象指向空间
//我们期望123,但多线程下可能会遇到:
//线程A: 132;
//线程B看到对象指向的空间不为null,因此就会直接return LazyMan对象,
//但是此时还没有完成构造方法,造成指向的对象时一块虚无的地址
}
}
}
return LazyMan;
}
//多线程并发条件下会出现问题
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
3. 静态内部类实现
通过静态内部类调用单例模式
package singleton;
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return innerClass.Holder;
}
//静态内部类的实现
public static class innerClass{
private static final Holder Holder = new Holder();
}
public static void main(String[] args) {
}
}
总结:
会遇到反射,以上三种方法都是不安全的。
4. 反射:
反射走的是无参构造器,我们可以在无参构造器里面添加判断,来防止反射破坏单例模式
情况一:构造方法构造一个实例之后再使用反射构造一个实例,利用构造器里面添加第三重检测和锁来防止反射破坏单例模式
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class) {
if(LazyMan != null) { //第三重检测,以及加锁
throw new RuntimeException("不要试图通过反射破坏单例模式!");
}
}
}
//DCL + volatile——》 双重检测锁 + 原子性操作
//双重检测 锁 模式的懒汉式单例——》DCL
//DCL也会出现问题,因为LazyMan = new LazyMna();的时候并不是原子操作,会出现指令重排的现象
//因此需要进一步改进,添加volatile关键字保证原子性操作
public static volatile LazyMan LazyMan;
public static LazyMan getInstance(){
if(LazyMan == null){
synchronized (LazyMan.class) {
if (LazyMan == null) {
LazyMan = new LazyMan(); //不是原子性操作
//1. 分配内存空间
//2. 执行构造方法,初始化对象
//3. 将对象指向空间
//我们期望123,但多线程下可能会遇到:
//线程A: 132;
//线程B看到对象指向的空间不为null,因此就会直接return LazyMan对象,
//但是此时还没有完成构造方法,造成指向的对象时一块虚无的地址
}
}
}
return LazyMan;
}
//多线程并发条件下会出现问题
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
情况二:两个实例都使用反射构造,利用红绿灯防止破坏。
设置一个标志位flag经过加密,这样可以防止单例模式被破坏。
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
public class LazyMan {
private static boolean flag = false;
private LazyMan(){
synchronized (LazyMan.class) {
if(flag == false) {
flag = true;
}else{
throw new RuntimeException("不要试图通过反射破坏单例模式!");
}
}
}
public static volatile 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) throws Exception {
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
但是极限情况下有可能标志位被破解,找到标志位之后也进行反射,修改权限
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
public class LazyMan {
private static boolean flag = false;
private LazyMan(){
synchronized (LazyMan.class) {
if(flag == false) {
flag = true;
}else{
throw new RuntimeException("不要试图通过反射破坏单例模式!");
}
}
}
public static volatile 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) throws Exception {
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);//破坏标志位的私有权限,通过反射构造一个实例之后,再把flag的值改回来,这样下次反射又可以构造实例
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
flag.set(instace1, false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
5. 枚举类型:
5. 枚举类型:
可以防止反射破坏单例模式,会抛出反射异常
枚举没有无参构造,只有有参构造,并且有两个参数(String,int)
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test {
public static void main(String[] args) throws Exception,NoSuchMethodException, IllegalAccessException,InvocationTargetException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}



