單例模式(反射破壞-列舉)
餓漢式單例
package com.jan.single;
//餓漢式單例
public class Hungry {
//一上來就會加載好,可能會浪費空間
private byte[] data11=new byte[1024*1024];
private byte[] data12=new byte[1024*1024];
private byte[] data13=new byte[1024*1024];
private byte[] data14=new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();//保證是唯一的了
public static Hungry getInstance(){ //一上來就加載了
return HUNGRY;
}
}
懶漢式單例
package com.jan.single;
//懶漢式單例
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;
}
//多執行緒并發
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
此時運行結果:單例有問題,偶爾會有多執行緒

靜態內部類:
package com.jan.single;
//靜態內部類
public class Holder {
private Holder(){ //構造器私有
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
進階-->加鎖
package com.jan.single;
//懶漢式單例
public class LazyMan {
private LazyMan(){ ///構造器私有
System.out.println(Thread.currentThread().getName()+"ok");
}
//為了安全,避免 lazyMan 指令重排,必須加volatile
private volatile static LazyMan lazyMan;//物件
public static LazyMan getInstance(){ //這代碼安全嗎?
//雙重檢測鎖模式的 懶漢式單例 DCL模式
if (lazyMan==null){
synchronized (LazyMan.class){ //保證這個類 LazyMan.class 只有一個
if(lazyMan==null){
lazyMan = new LazyMan(); //不是原子性操作
/*
* 1. 分配記憶體空間
* 2. 執行構造方法,初始化物件
* 3. 把這個物件指向這個空間
*
* 期望執行 123
* 132 A(先占記憶體空間)
* 若此時有 B執行緒 基于A,系統會認為 lazyMan 不為Null 直接return
* //此時lazyMan沒有完成構造
* */
}
}
}
return lazyMan;
}
//多執行緒并發
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
單例不安全,反射
列舉
炫技時刻 :反射破壞單例
package com.jan.single;
import java.lang.reflect.Constructor;
//懶漢式單例
public class LazyMan {
private LazyMan(){ ///構造器私有
System.out.println(Thread.currentThread().getName()+"ok");
}
//為了安全,避免 lazyMan 指令重排,必須加volatile
private volatile static LazyMan lazyMan;//物件
public static LazyMan getInstance(){ //這代碼安全嗎?
//雙重檢測鎖模式的 懶漢式單例 DCL模式
if (lazyMan==null){
synchronized (LazyMan.class){ //保證這個類 LazyMan.class 只有一個
if(lazyMan==null){
lazyMan = new LazyMan(); //不是原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//獲得空參構造器
declaredConstructor.setAccessible(true);//破壞私有權限,無視私有的構造器 通過反射來創建物件
LazyMan instance2= declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
結果:

繼續改進:不被反射破壞(三重檢測)
package com.jan.single;
import java.lang.reflect.Constructor;
//懶漢式單例
public class LazyMan {
private LazyMan(){ ///構造器私有
synchronized (LazyMan.class){ //這里加一把鎖,第二次用就會報錯
if (lazyMan!=null){
throw new RuntimeException("不要試圖使用反射破壞例外");
}
}
}
//為了安全,避免 lazyMan 指令重排,必須加volatile
private volatile static LazyMan lazyMan;//物件
public static LazyMan getInstance(){ //這代碼安全嗎?
//雙重檢測鎖模式的 懶漢式單例 DCL模式
if (lazyMan==null){
synchronized (LazyMan.class){ //保證這個類 LazyMan.class 只有一個
if(lazyMan==null){
lazyMan = new LazyMan(); //不是原子性操作
/*
* 1. 分配記憶體空間
* 2. 執行構造方法,初始化物件
* 3. 把這個物件指向這個空間
*
* 期望執行 123
* 132 A(先占記憶體空間)
* 若此時有 B執行緒 基于A,系統會認為 lazyMan 不為Null 直接return
* //此時lazyMan沒有完成構造
* */
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//獲得空參構造器
declaredConstructor.setAccessible(true);//破壞私有權限,無視私有的構造器 通過反射來創建物件
LazyMan instance2= declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
結果:

繼續破壞:
如果兩個物件都是LazyMan instance2= declaredConstructor.newInstance();這樣獲得,單例又被破壞
解決:加密(jan)
package com.jan.single;
import java.lang.reflect.Constructor;
//懶漢式單例
public class LazyMan {
private static boolean jan = false;//定義一個變數
private LazyMan(){ ///構造器私有
synchronized (LazyMan.class){ //這里加一把鎖,第二次用就會報錯
if (jan==false){
jan = true; //這個肯定會變
}else {
throw new RuntimeException("不要試圖使用反射破壞例外");
}
}
}
//為了安全,避免 lazyMan 指令重排,必須加volatile
private volatile static LazyMan lazyMan;//物件
public static LazyMan getInstance(){ //這代碼安全嗎?
//雙重檢測鎖模式的 懶漢式單例 DCL模式
if (lazyMan==null){
synchronized (LazyMan.class){ //保證這個類 LazyMan.class 只有一個
if(lazyMan==null){
lazyMan = new LazyMan(); //不是原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//獲得空參構造器
declaredConstructor.setAccessible(true);//破壞私有權限,無視私有的構造器 通過反射來創建物件
LazyMan instance2= declaredConstructor.newInstance();
LazyMan instance1= declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
再破壞:
通過獲取加密欄位,從而破壞私有權限,再設定加密欄位.instance = false
package com.jan.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
//懶漢式單例
//道高一尺魔高一丈
public class LazyMan {
private static boolean jan = false;//定義一個變數
private LazyMan(){ ///構造器私有
synchronized (LazyMan.class){ //這里加一把鎖,第二次用就會報錯
if (jan==false){
jan = true; //這個肯定會變
}else {
throw new RuntimeException("不要試圖使用反射破壞例外");
}
}
}
//為了安全,避免 lazyMan 指令重排,必須加volatile
private volatile static LazyMan lazyMan;//物件
public static LazyMan getInstance(){ //這代碼安全嗎?
//雙重檢測鎖模式的 懶漢式單例 DCL模式
if (lazyMan==null){
synchronized (LazyMan.class){ //保證這個類 LazyMan.class 只有一個
if(lazyMan==null){
lazyMan = new LazyMan(); //不是原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
//LazyMan instance1 =
Field jan = LazyMan.class.getDeclaredField("jan");//獲取欄位
jan.setAccessible(true);//破壞私有權限
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//獲得空參構造器
declaredConstructor.setAccessible(true);//破壞私有權限,無視私有的構造器 通過反射來創建物件
LazyMan instance2= declaredConstructor.newInstance();
jan.set(instance2,false);
LazyMan instance1= declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
結果:

萬惡的反射怎么解決:列舉
反編譯jad:https://varaneckas.com/jad/
package com.jan.single;
//列舉 是一個什么? 本身也是class 類
public enum EnumSingle {
INSTANCE;//不可能被拿走
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{ //先保證物件是唯一的,測驗一下
public static void main(String[] args) {
EnumSingle instance1= EnumSingle.INSTANCE;
EnumSingle instance2= EnumSingle.INSTANCE;
System.out.println(instance1);
System.out.println(instance2);
}
}
結果:

看原始碼,有一個無參構造

在測驗也使用無參構造
package com.jan.single;
import java.lang.reflect.Constructor;
//列舉 是一個什么? 本身也是class 類
public enum EnumSingle {
INSTANCE;//不可能被拿走
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{ //試一試能不能破壞,看源代碼 有一個無參構造
public static void main(String[] args) throws Exception {
EnumSingle instance1= EnumSingle.INSTANCE; //有一個無參構造,試一試,看看是不是真的
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
//com.jan.single.EnumSingle.<init>()
//(newInstance 原始碼)正常報錯是:throw new IllegalArgumentException("Cannot reflectively create enum objects");
System.out.println(instance1);
System.out.println(instance2);
}
}
結果:被欺騙,這原始碼不是真的

因為newinstance的原始碼:

兩個輸出的結果不同!!!
反編譯
在IDEA的targer中找到 EnumSingle.class,打開檔案的檔案的位置,再從檔案的衛視輸入 cmd
也有空參,也是騙我們的

更專業的反編譯
jad

這里是有參構造

修改代碼,這里只展示不同的
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
結果:這個反射的確不能破壞列舉的單例

列舉最終反編譯原始碼
// 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 com.jan.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/jan/single/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
});
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553937.html
標籤:Java
下一篇:返回列表
