饿汉式单例
Hungry.java
/**
* @Description 饿汉式单例
*/
public class Hungry {
// 私有构造方法,不准其他类使用new创建
private Hungry() {
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
存在的问题:如果Hungry类中存在大量的初始化内容会造成空间浪费,示例如下:
public class Hungry {
// data1、data2、data3 此处是指存在大量的初始化内容,使用饿汉式单例的话会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
// 私有构造方法,不准其他类使用new创建
private Hungry() {
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
懒汉式单例
单线程的简单单例模式
注:单线程情况下安全,多线程情况下会出现问题。
/**
* @Description 懒汉式单例
*/
public class LazyMan {
// 私有构造方法,不准其他类使用new创建
private LazyMan() {
System.out.println(Thread.currentThread().getName());
}
private static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
getInstance();
}).start();
}
}
}
输出信息:(多线程情况运行处的结果不同)
Thread-0
Thread-2
Thread-1
DCL单例模式 (双重检测锁模式的单例模式)
上述单线程代码getInstance()创建的单例,单线程情况下线程安全,多线程情况下会存在创建多个。可以加锁解决这种问题。
代码如下:
/**
* @Description 懒汉式单例模式-DCL单例模式(双重检测锁模式单例模式)
*/
public class LazyMan {
// 私有构造方法,不准其他类使用new创建
private LazyMan() {
}
// volatile保证可见性和禁止指令重排
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if(lazyMan == null){
synchronized(LazyMan.class){
if (lazyMan == null) {
lazyMan = new LazyMan();// 不是一个原子性操作
}
}
}
return lazyMan;
}
}
注:实例对象需要使用volatile修饰,保证可见性和禁止指令重排。
LazyMan lazyMan = new LazyMan();
步骤拆分如下:
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
指令重排指:
执行顺序可能是123也可能是132,
如果A线程执行132步骤的时候,
B线程进入getInstance()方法会直接返回lazyMan(此时的lazyMan可能还没有完成构建)
所以此处的实例对象需要使用volatile修饰,保证可见性和禁止指令重排。
这种方式存在的问题,可以用反射来破坏单例模式
public static void main(String[] args) throws Exception {
LazyMan lazyMan1 = getInstance();
// 反射方式破坏单例模式
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
输出结果:
com.xiangty.single.LazyMan@1540e19d
com.xiangty.single.LazyMan@677327b6
可以使用一个私有标识来避免上述的反射造成的问题,代码如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* @Description 懒汉式单例模式-DCL单例模式(双重检测锁模式单例模式)
* 添加一个如下的flag标识来避免反射。 (还是会存在问题,反射可以修改flag的值)
*/
public class LazyMan3 {
private static boolean flag = false;
// volatile保证可见性和禁止指令重排
private volatile static LazyMan3 lazyMan;
// 私有构造方法,不准其他类使用new创建
private LazyMan3() {
if(flag){
throw new RuntimeException("对象已存在");
} else {
flag = true;
}
}
public static LazyMan3 getInstance() {
if (lazyMan == null) {
synchronized (LazyMan3.class) {
if (lazyMan == null) {
lazyMan = new LazyMan3(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
// verification();
editProperties();
}
/**
* 不修改flag的,验证方法,会出现异常
*
* @throws Exception
*/
public static void verification() throws Exception {
// LazyMan3 lazyMan1 = getInstance();
Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazyMan3 lazyMan2 = constructor.newInstance();
// LazyMan3 lazyMan1 = constructor.newInstance();
LazyMan3 lazyMan1 = getInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
/**
* 修改flag属性值的破坏方法,不会出现异常,会声明两对象
*
* @throws Exception
*/
public static void editProperties() throws Exception {
Field field = LazyMan3.class.getDeclaredField("flag");
field.setAccessible(true);
Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazyMan3 lazyMan2 = constructor.newInstance();
// flag重设值
field.set(lazyMan2, false);
LazyMan3 lazyMan1 = constructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
}
不能使用反射破坏枚举
普通的类都可以通过反射破坏单例模式。查看Constructor.java源码发现 不能使用反射破坏枚举。
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");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
定义一个枚举类EnumSingle.java
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
使用反射方式的测试类一
public class NoParametersTestEnumSingle{
/**
* 无参的构造方法反射
* 异常信息:Exception in thread "main" java.lang.NoSuchMethodException: com.xiangty.single.EnumSingle.<init>()
*/
public static void main(String[] args) throws Exception {
EnumSingle enumSingle1 = EnumSingle.INSTANCE;
// 没有无参的构造方法
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
EnumSingle enumSingle2 = constructor.newInstance();
System.out.println(enumSingle1);
System.out.println(enumSingle2);
}
}
使用反射中无参的构造方法,提示的异常信息:Exception in thread “main” java.lang.NoSuchMethodException: com.xiangty.single.EnumSingle.() ,没有无参的构造方法。这个信息并不是源码中提及的"Cannot reflectively create enum objects"。
查看IDEA中的EnumSingle.class的编译文件如下:
public enum EnumSingle {
INSTANCE;
private EnumSingle() {
}
public EnumSingle getInstance() {
return INSTANCE;
}
}
编译文件中有无参的构造方法,那为什么我们使用反射无参构造的时候会出异常呢?此处需要借用jad.exe反编译工具来查看EnumSingle.java底层的实现是怎么样的。
使用jad.exe软件反编译EnumSingle.class然后可以得到EnumSingle.java内容如下:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
我们发现定义的枚举类没有无参的构造方法,存在有参构造方法,方法如下。
private EnumSingle(String s, int i)
{
super(s, i);
}
编写有参构造反射方式的方法,代码如下:
public class HavaParametersTest{
/**
* 有参的构造方法反射
* 异常信息:Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
*/
public static void main(String[] args) throws Exception {
EnumSingle enumSingle1 = EnumSingle.INSTANCE;
// 没有无参的构造方法
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingle enumSingle2 = constructor.newInstance();
System.out.println(enumSingle1);
System.out.println(enumSingle2);
}
}
使用有参构造反射的方式,就可以到了与源码一样的异常信息:Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects。