//饿汉式
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 (){
}
private final static Hungry Hungry = new Hungry();
public static Hungry getInstance(){
return Hungry;
}
}
//懒汉式
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private static LazyMan LazyMan;
public static LazyMan getInstance(){
if (LazyMan == null){
LazyMan = new LazyMan();
}
return LazyMan;
}
//单线程OK
//多线程并发不OK,起10个线程看一下
public static void main(String[] ages){
for (int i = 0; i < 10; i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
执行后会发现他已经乱掉了,之后我们加一把锁,再跑一下。
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private static LazyMan LazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (LazyMan == null){
//类加锁 synchronized
synchronized (LazyMan.class){
if (LazyMan == null){
LazyMan = new LazyMan();
}
}
}
return LazyMan;
}
public static void main(String[] ages){
for (int i = 0; i < 10; i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
不管怎么跑都是一个,但是 LazyMan = new LazyMan();在极端情况下是有问题的,因为他不是一个原子性操作。
if (LazyMan == null){
//类加锁 synchronized
synchronized (LazyMan.class){
if (LazyMan == null){
LazyMan = new LazyMan();//不是一个原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*
* 123 理想化
* 132 A
* B 此时LazyMan还未完成构造
*/
}
}
}
return LazyMan;
}
他会经历以下步骤
这个时候他可能会发生指令重排现象比如,我期望他执行123操作,但他真实可能执行132操作
比如走向了132 A线程他是没有问题的,但是突然来了一条B线程,线程B由于他已经把对象指向这个空间了,他会认为LazyMan 不等于 null 会直接return LazyMan,但此时 LazyMan还没有完成构造,他还是空的,所以可能会发生指令重排。为了防止这种现象。可以使用volatile避免进行指令重排。
这就是完整的双重检测锁+原子性操作。
//懒汉式
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
//避免指令重排 volatile
private volatile static LazyMan LazyMan;
//双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (LazyMan == null){
//类加锁 synchronized
synchronized (LazyMan.class){
if (LazyMan == null){
LazyMan = new LazyMan();
}
}
}
return LazyMan;
}
public static void main(String[] ages){
for (int i = 0; i < 10; i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.Holder;
}
public static class InnerClass{
private static final Holder Holder = new Holder();
}
}
它可以加载外部类不会加载内部类,这样可以实现懒加载,节省资源。但是他是不安全的。因为java有反射机制。
首先获得第一个对象
import java.lang.reflect.Constructor;
//懒汉式
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static 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[] ages) throws Exception {
LazyMan instance = LazyMan.getInstance();//获得第一个对象
LazyMan instance2 = LazyMan.getInstance();
}
}
正常情况下上面两个对象应该是相等的。但是如果用反射去破坏这个单例呢。
import java.lang.reflect.Constructor;
//懒汉式
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static 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[] ages) throws Exception {
LazyMan instance = LazyMan.getInstance();//获得第一个对象
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器 抛出异常
declaredConstructor.setAccessible(true);//暴力反射 无视私有构造器
LazyMan instance2 = declaredConstructor.newInstance(); //通过反射创建一个对象
System.out.println(instance);
System.out.println(instance2);
}
}
按照单例来说这两个值应该相等,所以得出结论
当然也可以去解决这个问题,代码中无参走的是 private LazyMan(){},在这里面加一把锁
package designModel.Singleton;
import java.lang.reflect.Constructor;
//懒汉式
public class LazyMan {
private LazyMan(){
//类加锁
synchronized (LazyMan.class){
if(LazyMan != null){
throw new RuntimeException("不要使用反射破坏异常");
}
}
}
private volatile static 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[] ages) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
这种反射就破坏失败了,但是还有问题,上面代码已经把双重检测变成了三重检测,但是第一个线程默认使用的
LazyMan instance = LazyMan.getInstance();
.getInstance去得到的,但当我两个对象都是使用
LazyMan instance = declaredConstructor.newInstance();
执行
import java.lang.reflect.Constructor;
//懒汉式
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if(LazyMan != null){
throw new RuntimeException("不要使用反射破坏异常");
}
}
}
private volatile static 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[] ages) throws Exception {
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance(); //通过反射创建对象
LazyMan instance2 = declaredConstructor.newInstance(); //通过反射创建对象
System.out.println(instance);
System.out.println(instance2);
}
}
然而单例又被破坏了。当然这也可以解决。
首先定义一个变量nica啥都行也可以传密钥。
import java.lang.reflect.Constructor;
//懒汉式
public class LazyMan {
//创建一个标志位
private static boolean nicai = false;
private LazyMan(){
synchronized (LazyMan.class){
//判断
if(nicai == false){
nicai = true;
}else {
throw new RuntimeException("不要使用反射破坏异常");
}
}
}
private volatile static 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[] ages) throws Exception {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
第一次执行无论执不执行反射判断都会变,除非反编译反射是找不到定义的变量的,再加一些加密处理会更安全。
但是,定义的nicai这个变量不能够被破坏吗,加密也是能够解密的。
假如我找到了nicai这个变量。
import javax.swing.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
//懒汉式
public class LazyMan {
private static boolean nicai = false;
private LazyMan(){
synchronized (LazyMan.class){
if(nicai == false){
nicai = true;
}else {
throw new RuntimeException("不要使用反射破坏异常");
}
}
}
private volatile static 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[] ages) throws Exception {
//找到变量
Field nicai = LazyMan.class.getDeclaredField("nicai");
nicai.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
//赋值
nicai.set(instance,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
单例又被破坏了,得出结论
接下来看源码寻找答案。
查看newInstance源码
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, clazz, modifiers);
}
//如果这个类型是一个枚举类型会抛出 Cannot reflectively create enum objects(无法以反射方式创建枚举对象)
//枚举来源jdk1.5它自带单例模式
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;
}
枚举
枚举是什么? 本身也是一个class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args){
EnumSingle instance = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
System.out.println(instance);
System.out.println(instance2);
}
}
试一下用反射破坏枚举
首先分析源码看一下枚举是有参还是有参构造。
空参的,然后用反射去破坏枚举
import java.lang.reflect.Constructor;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
但是这个结果不太对,结果写的是NoSuchMethodException: design.Singleton.EnumSingle(类中没有空参构造器),但是正常看枚举源码报的错应该是Cannot reflectively create enum objects(无法以反射方式创建枚举对象)。完蛋。对不上啊。继续研究。
首先IDEA看源码上面写了,有个空参构造方法,但是跑过之后发现里面没有这个东西。
IDEA已经看不出来了
通过源码反编译代码去寻找问题
找到源代码文件夹。
CMD模式启动,反编译EnumSingle.class
javap -p -[类名] # 对javac编译的文件进行反编译
首先会看到这个这个枚举本身也是一个Class只是继承了枚举的类
public final class design.Singleton.EnumSingle extends java.lang.Enum<design.Singleton.EnumSingle> //继承了枚举的类
{
public static final design.Singleton.EnumSingle INSTANCE;
private static final design.Singleton.EnumSingle[] $VALUES;
public static design.Singleton.EnumSingle[] values();
public static design.Singleton.EnumSingle valueOf(java.lang.String);
private design.Singleton.EnumSingle();//有一个空参的构造
public design.Singleton.EnumSingle getInstance();
static {};
}
但是反编译之后发现也是有一个空参的构造和IDEA看的一样。
换一个更专业的软件去反编译再看看
什么是jad
下载jad.exe
下载好的jad.exe放到源码目录下
指定命令将源码.Class变成一个java文件
jad -sjava [.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
package design.Singleton;
public final class EnumSingle extends Enum //还是继承
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(design/Singleton/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
});
}
}
还是继承了枚举类,但是上面的代码中存在有参构造器,一个String,一个int。
回到IDEA编写反射枚举代码
import java.lang.reflect.Constructor;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//更改了类型
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
这下是对了。绝了 。结论
代码开源上面的例子都在下面的连接里用Git直接拉就行
https://gitee.com/DH_2/DesignPatterns.git