單例模式,反射破環?
阿新 • • 發佈:2020-05-30
> 餓漢式
```java
// 餓漢式單例
public class Hungry {
//構造器私有
private Hungry(){
}
// 一上來就把這個類載入了
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
```
```java
// 餓漢式單例
public class Hungry {
// 這4組資料非常耗記憶體資源,餓漢式一上來就把所有的記憶體裡面的東西全部載入進來了,就存在這個空間
// 但這個空間現在是沒有使用的,可能會造成浪費空間
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;
}
}
```
餓漢式單例可能會造成浪費空間,所以想要用的時候再去建立這個物件,平時就先放在這個地方,於是就出現了懶漢式!
> 懶漢式
```java
// 懶漢式單例
public class LazyMan {
// 構造器私有
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
```
它是有問題的,單執行緒下確實單例ok,多執行緒併發就會出現問題!
**測試**
```java
// 懶漢式單例
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();
}
}
}
```
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085229819-614760537.png)
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085216861-466747495.png)
發現單例有問題,每次結果可能都不一樣!
**解決**
```java
// 懶漢式單例
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 (LazyMan.class){
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();
}
}
}
```
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085200698-203464594.png)
但在極端情況下還是可能出現問題
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085141489-348383799.png)
經歷三個步驟:
1、 分配記憶體空間
2、 執行構造方法,初始化物件
3、 把這個物件指向這個空間
**有可能會發生指令重排的操作!**
比如,期望它執行 123 ,但是它真實可能執行132,比如第一個A執行緒過來執行了132,先分配空間再吧這個空間佔用了,佔用之後再去執行構造方法,如果現在突然來了個B執行緒,由於A已經指向這個空間了,它會以為這個 lazyMan 不等於 null ,直接return ,此時lazyMan還沒有完成構造,所以必須避免這個問題!
必須加上`volatile`
```java
// 懶漢式單例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
// 避免指令重排
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
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();
}
}
}
```
> 靜態內部類
```java
// 靜態內部類
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
```
也是單例模式的一種,不安全!
> 單例不安全 反射
```java
// 懶漢式單例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
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 {
LazyMan instance1 = LazyMan.getInstance();
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 無視了私有的構造器
// 通過反射建立物件
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
```
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085115451-983189446.png)
結論:反射可以破壞這種單例
**解決**
```java
// 懶漢式單例
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要試圖使用反射破環 異常");
}
}
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
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 {
LazyMan instance1 = LazyMan.getInstance();
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 無視了私有的構造器
// 通過反射建立物件
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
```
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085051712-1140711830.png)
但是如果都用反射建立物件的情況下,還是會破環單例!
**測試**
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085022776-883662583.png)
**解決**
```java
// 懶漢式單例
public class LazyMan {
// 標誌位
private static boolean abc = false;
private LazyMan(){
synchronized (LazyMan.class){
if (abc==false){
abc=true;
}else {
throw new RuntimeException("不要試圖使用反射破環 異常");
}
}
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
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 {
//LazyMan instance1 = LazyMan.getInstance();
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 無視了私有的構造器
// 通過反射建立物件
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
```
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085004471-1299363875.png)
但是如果被人知道 `abc`這個變數,也可以破環!
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084949083-762775567.png)
==單例又被破環了!==
看一下原始碼
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084934727-1890738756.png)
它說不能使用反射破環列舉,列舉是jdk1.5出現的,自帶單例模式!
**測試**,寫一個列舉類
```java
// enum 本身就是一個class類
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
```
檢視它的原始碼
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084906146-1966744325.png)
試圖破環!
```java
// enum 本身就是一個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 declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
```
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084842906-1366055976.png)
它竟然說我現在的這個列舉類中沒有空參構造器!
然後就去原始碼裡分析!
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084830441-2061828793.png)
找到這個class檔案!利用javap反編譯一下!
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084816237-568885889.png)
發現這個也顯示有一個空參構造,證明這個也不對,用第三方的工具檢視!
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084759223-356213598.png)
利用它再吧class檔案生成java檔案!
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084739520-1898020039.png)
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084730518-806779912.png)
開啟這個java檔案
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084718059-24953770.png)
**證明是idea和原始碼騙了我!**
再次嘗試破環!
```java
// enum 本身就是一個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 declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
```
![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084700911-1549753380.png)
**結論:反射無法破環列舉類!**