- 不让用户去自主的创建实例对象,而是自己创建好实例对象,让用户通过设置好的方法去调用
package com.cx;
public class SingletonHung {
//事先准备实例对象
private static SingletonHung singletonHung=new SingletonHung();
//构造器私有,用户不能直接通过构造器来创建对象了
private SingletonHung() {
}
//提供一个方法对外使用
public static SingletonHung getSingletonHung(){
return singletonHung;
}
}
懒汉式
- 使用的时候再去创建
package com.cx;
public class SingletonLazy {
private static SingletonHung singletonHung=null;
//构造器私有
private SingletonLazy() {
}
public static SingletonHung singletonHungInstance(){
if (singletonHung==null){
singletonHung=new SingletonHung();
}
return singletonHung;
}
}
注意:上述在多线程中,容易出现线程安全问题
DCL懒汉式DCL单例模式:Double Check Lock缩写,即双重检查锁定,是单例模式中线程安全的懒汉式单例模式
package com.cx;
public class SingletonLazy {
private static SingletonLazy singletonLazy=null;
//构造器私有
private SingletonLazy() {
}
//双重检测模式
public static SingletonLazy singletonLazyInstance(){
if (singletonLazy==null){
synchronized (SingletonLazy .class){
if (singletonLazy==null){
singletonLazy=new SingletonLazy();
}
}
}
return singletonLazy;
}
}
此时是否就安全了?
极端情况下,此模式也不安全,原因如下:
singletonHung=new SingletonHung();
此步骤不是一个原子性操作,实际上是有三步操作:
1. 分配内存空间 2. 执行构造方法,初始化对象 3. 把该对象指向这个空间
那么可能就会出现指令重排,即,若线程A执行顺序为1-3-2,线程B执行顺序为1-2-3;那么当线程A执行到1-3时,此时还没有实际生成对象,但是线程B认为singletonHung!=null,那么就会直接走return,那么线程B的得到的是没有完成构造的对象。那么现在为了避免指令重排,就要使用volatile关键字避免指令重排
package com.cx;
public class SingletonLazy {
//volatile 避免指令重排
private static volatile SingletonLazy singletonLazy=null;
//构造器私有
private SingletonLazy() {
}
//双重检测模式
public static SingletonLazy singletonLazyInstance(){
if (singletonLazy==null){
synchronized (SingletonLazy.class){
if (singletonLazy==null){
singletonLazy=new SingletonLazy();
}
}
}
return singletonLazy;
}
}
那么经过上述操作了之后,是不是就安全了?
显然也不是很安全,因为还有一种方法可以破坏此模式
package com.cx;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test29 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
SingletonLazy singletonLazyInstance = SingletonLazy.getSingletonLazyInstance();
Constructor declaredConstructor = SingletonLazy.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有
SingletonLazy singletonLazy = declaredConstructor.newInstance();
//得到的两个对象不一样
System.out.println(singletonLazyInstance);
System.out.println(singletonLazy);
}
}
可以知道该种方法是通过反射,得到私有构造器,通过私有构造器来创建对象,所以我们可以通过对构造器加一些控制来避免这种方式的破坏
package com.cx;
public class SingletonLazy {
//volatile 避免指令重排
private static volatile SingletonLazy singletonLazy=null;
//构造器私有
private SingletonLazy() {
if (singletonLazy!=null){
throw new RuntimeException("不要视图通过使用反射破坏异常!");
}
}
//双重检测模式
public static SingletonLazy getSingletonLazyInstance(){
if (singletonLazy==null){
synchronized (SingletonLazy.class){
if (singletonLazy==null){
singletonLazy=new SingletonLazy();
}
}
}
return singletonLazy;
}
}
那么如果两个都使用反射来构造对象?
反射破坏单例模式2package com.cx;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test29 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Constructor declaredConstructor = SingletonLazy.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有
SingletonLazy singletonLazy = declaredConstructor.newInstance();
SingletonLazy singletonLazy2 = declaredConstructor.newInstance();
//得到的两个对象不一样
System.out.println(singletonLazy);
System.out.println(singletonLazy2);
}
}
那如何避免这种情况?可以通过添加标志量来避免
package com.cx;
public class SingletonLazy {
//标志位
private static boolean flag=false;
//volatile 避免指令重排
private static volatile SingletonLazy singletonLazy=null;
//构造器私有
private SingletonLazy() {
if (flag==false){
flag=true;
}else{
throw new RuntimeException("不要视图通过使用反射破坏异常!");
}
if (singletonLazy!=null){
throw new RuntimeException("不要视图通过使用反射破坏异常!");
}
}
//双重检测模式
public static SingletonLazy getSingletonLazyInstance(){
if (singletonLazy==null){
synchronized (SingletonLazy.class){
if (singletonLazy==null){
singletonLazy=new SingletonLazy();
}
}
}
return singletonLazy;
}
}
那么此做法是不是就不会再出错了?显然不是,我们还可以通过反射获取其私有属性,进行修改
反射破坏单例模式3package com.cx;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class Test29 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Constructor declaredConstructor = SingletonLazy.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有
Field flag = SingletonLazy.class.getDeclaredField("flag");
flag.setAccessible(true);
SingletonLazy singletonLazy = declaredConstructor.newInstance();
flag.set("flag",false);//又将其修改回来
SingletonLazy singletonLazy2 = declaredConstructor.newInstance();
//得到的两个对象不一样
System.out.println(singletonLazy);
System.out.println(singletonLazy2);
}
}
…
可以发现,某前使用的这些方法,总会有另一种方法来破坏单例模式,那是否有一种操作,使其无法通过反射来构造对象,从根本上解决这个问题?
答案是有的,那就是通过枚举,在通过构造器的newInstance来创建对象中,明确的表示:Cannot reflectively create enum objects
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
枚举类实现单例模式
Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。
package com.cx;
public enum SingletonEnum {
INSTANCE;
//volatile 避免指令重排
private volatile User user = null;
//构造器私有
private SingletonEnum() {
user = new User();
}
//提供单例
public User getUser(){
return user;
}
}
package com.cx;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test29 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
User user = SingletonEnum.INSTANCE.getUser();
User user2 = SingletonEnum.INSTANCE.getUser();
//一模一样,构造器只会执行一次
System.out.println(user.hashCode());
System.out.println(user2.hashCode());
System.out.println("------------------");
Constructor declaredConstructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
//这一步就会报异常 Cannot reflectively create enum objects
SingletonEnum singletonEnum = declaredConstructor.newInstance();
User user3 = singletonEnum.getUser();
System.out.println(user3.hashCode());
}
}
java-单例模式 推荐狂神说



