010:深入理解單例模式
- 1 餓漢式模式
- 2 懶漢式模式(執行緒不安全)
- 3 雙重檢驗鎖原理
- 4 靜態內部類方式
- 5 使用反射技術破解單例
- 6 使用序列化破解單例
- 7 列舉方式防止破解
1 餓漢式模式
課程內容:
1、什么是單例模式?單例的應用場景
2、完全解密單例的七種寫法
3、如何去破解一個單例,創建多次
4、如何防止反射、序列化破解單例
什么是單例模式
單例模式確保一個類只有一個實體,并提供一個全域訪問點,實作單例模式的方法是私有化建構式,通過getInstance()方法實體化物件,并回傳這個實體,
單例模式優缺點
優點:
1、單例類只有一個實體
2、共享資源,全域使用
3、節省創建時間,提高性能
缺點:可能存在執行緒不安全的問題
單例的七種寫法
分別是「餓漢」、「懶漢(非執行緒安全)」、「懶漢(執行緒安全)」、「雙重校驗鎖」、「靜態內部類」、「列舉」和「容器類管理」
餓漢式
public class SingletonV1 {
/**
* 餓漢式 優點:先天執行緒安全 當類被加載的時候就會創建該物件
* 缺點:如果專案中使用過多餓漢式,專案在啟動的時候非常慢,存放在方法區占用記憶體比較大
* 如果用戶不使用該物件的時候,也會被提前創建,浪費資源
*/
private static SingletonV1 singletonV1 = new SingletonV1();
private SingletonV1() {
}
/**
* 回傳該物件的實體
*
* @return
*/
public static SingletonV1 getInstance() {
return singletonV1;
}
}
public class SingletonV1Test {
public static void main(String[] args) {
SingletonV1 instance1 = SingletonV1.getInstance();
SingletonV1 instance2 = SingletonV1.getInstance();
System.out.println(instance1 == instance2); // true
}
}
優點:先天性執行緒是安全的,當類初始化的時候就會創建該物件
缺點:如果餓漢式使用過多,可能會影響專案啟動的效率問題,
2 懶漢式模式(執行緒不安全)
懶漢式(執行緒不安全)
public class SingletonV2 {
// 懶漢式 當真正需要使用該物件的時候才會被初始化
private static SingletonV2 singletonV2;
private SingletonV2(){}
/**
* 可能存在執行緒安全問題,在多執行緒的情況下,可能會被初始化多次
* @return
*/
public static SingletonV2 getInstance(){
// 當第一次singletonV2等于null的情況下才會被初始化,第二次直接回傳物件
if(singletonV2 == null){
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
public class SingletonV2Test {
public static void main(String[] args) {
SingletonV2 instance1 = SingletonV2.getInstance();
SingletonV2 instance2 = SingletonV2.getInstance();
System.out.println(instance1 == instance2); // true
}
}
懶漢式模擬高并發場景執行緒不安全問題
public class SingletonV2 {
// 懶漢式 當真正需要使用該物件的時候才會被初始化
private static SingletonV2 singletonV2;
private SingletonV2(){}
/**
* 可能存在執行緒安全問題,在多執行緒的情況下,可能會被初始化多次
* @return
*/
public static SingletonV2 getInstance(){
// 當第一次singletonV2等于null的情況下才會被初始化,第二次直接回傳物件
if(singletonV2 == null){
try{
Thread.sleep(3000);
}catch(Exception e){
e.printStackTrace();
}
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
public class SingletonV2Test {
public static void main(String[] args) {
// 模擬高并發情況下 懶漢式執行緒安全問題
for (int i = 0; i < 300; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SingletonV2 singletonV2 = SingletonV2.getInstance();
System.out.println(Thread.currentThread().getName() + "," + singletonV2);
}
}).start();
}
}
}

在getInstance()方法上加上synchronized可以解決執行緒安全問題,但是運行如同單執行緒,效率較低,
3 雙重檢驗鎖原理
懶漢式解決執行緒安全問題為什么效率比較低?
執行緒安全問題,當多個執行緒共享同一個資料做寫的操作,可能會存在執行緒安全問題,讀不存在執行緒安全問題,
懶漢式解決執行緒安全(加上synchronized)讀和寫都加上了鎖,應該是第一次創建物件的時候才會加鎖,之后獲取該物件的時候不需要加鎖,
雙重檢驗鎖目的是什么?
解決懶漢式(執行緒安全)獲取物件效率問題,
雙重檢驗鎖
public class SingletonV3 {
/**
* volatile 防止重排序 java記憶體模型 保證可見性
*/
private volatile static SingletonV3 singletonV3;
// 雙重檢驗鎖 解決懶漢式讀和寫都加鎖
private SingletonV3(){}
public static SingletonV3 getInstance() {
// 當多個執行緒同時可能在new物件的時候,才會加鎖,保證執行緒安全問題
if (singletonV3 == null) {
synchronized (SingletonV3.class) {
if (singletonV3 == null) { // 當前執行緒已經獲取到鎖了,再判斷該物件是否已經初始化,沒有初始化的話開始創建
singletonV3 = new SingletonV3();
}
}
}
return singletonV3;
}
}
注:如果沒有第二個if (singletonV3 == null),假設第一個if (singletonV3 == null)同時有10個執行緒進入,synchronized只是保證一個執行緒獲取到鎖,第一個獲取鎖的執行緒最先創建物件,后面9個執行緒隨后獲取鎖同樣也會創建物件,導致一共創建10個物件,而如果有第二個if,那只有第一個獲取鎖的執行緒創建物件成功,后面執行緒不創建,保證單例,
測驗效果:
public class SingletonV3 {
/**
* volatile 防止重排序 java記憶體模型 保證可見性
*/
private volatile static SingletonV3 singletonV3;
// 雙重檢驗鎖 解決懶漢式讀和寫都加鎖
private SingletonV3(){}
public static SingletonV3 getInstance() {
// 當多個執行緒同時可能在new物件的時候,才會加鎖,保證執行緒安全問題
if (singletonV3 == null) {
try{
Thread.sleep(3000);
}catch(Exception e){
e.printStackTrace();
}
synchronized (SingletonV3.class) {
if (singletonV3 == null) { // 當前執行緒已經獲取到鎖了,再判斷該物件是否已經初始化,沒有初始化的話開始創建
singletonV3 = new SingletonV3();
}
}
}
return singletonV3;
}
}
public class SingletonV3Test {
public static void main(String[] args) {
// 模擬高并發情況下 懶漢式執行緒安全問題
for (int i = 0; i < 300; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SingletonV3 singletonV3 = SingletonV3.getInstance();
System.out.println(Thread.currentThread().getName() + "," + singletonV2);
}
}).start();
}
}
}
第一次執行(創建物件)慢,因為走了sleep方法,后面執行都快,
4 靜態內部類方式
如何解決寫和讀都不加鎖,還能保證唯一性,執行緒安全問題?
靜態內部類
public class SingletonV4 {
private SingletonV4() {
System.out.println("建構式被初始化...");
}
public static SingletonV4 getInstance() {
return SingletonV4Utils.singletonV4;
}
// 在類里面嵌套的
private static class SingletonV4Utils {
private static final SingletonV4 singletonV4 = new SingletonV4();
}
/**
* 內部類在呼叫的時候才會初始化singletonV4
* static 靜態 保證唯一
* @param args
*/
// 靜態內部類特征:繼承懶漢式和餓漢式優點、同時解決雙重檢驗鎖第一次加載慢的問題 讀和寫都不需要同步效率非常高...
public static void main(String[] args) {
System.out.println("專案啟動成功...");
SingletonV4 instance1 = SingletonV4.getInstance();
SingletonV4 instance2 = SingletonV4.getInstance();
System.out.println(instance1 == instance2); // true
}
}
懶加載:內部類在呼叫的時候才會創建物件
保證執行緒安全問題:靜態物件static保證唯一性

5 使用反射技術破解單例
單例基本原則:保證在單個jvm中不重復創建
雖然單例通過私有建構式,可以實作防止程式員初始化物件,但是還可以通過反射和序列化技術破解單例,
使用反射技術破解單例
public class SingletonV3Test2 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
SingletonV3 instance1 = SingletonV3.getInstance();
// 如何去破解單例 使用java反射技術、序列化技術
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
// 使用java的反射技術創建
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2); //false
}
}
如何防止被反射破解----私有化建構式
…
private SingletonV3() throws Exception {
if(singletonV3 !=null){
throw new Exception("物件已經被初始化...");
}
System.out.println("SingletonV3被初始化...");
}
…

6 使用序列化破解單例
反序列化創建物件破解單例
public class SingletonV5 implements Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// java的序列化技術
/**
* 物件從記憶體寫入到硬碟中 序列化
* 從硬碟中讀取到記憶體 反序列化
*/
SingletonV5 instance1 = SingletonV5.getInstance();
FileOutputStream fos = new FileOutputStream("D:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("D:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonV5 instance2 = (SingletonV5) ois.readObject();
System.out.println(instance1 == instance2); //false
}
private static SingletonV5 singletonV5 = new SingletonV5();
private SingletonV5() {}
public static SingletonV5 getInstance() {
return singletonV5;
}
/* //回傳序列化獲取物件 ,保證為單例
public Object readResolve() {
return singletonV5;
}*/
}


7 列舉方式防止破解
列舉方式防止破解
public enum EnumSingleton {
INSTANCE;
// 列舉能夠絕對有效的防止實體化多次,和防止反射和序列化破解
public void add() {
System.out.println("add方法...");
}
}
public class EnumSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1 == instance2);
instance1.add();
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton instance3 = declaredConstructor.newInstance();
System.out.println(instance3 == instance1);
}
}

列舉類沒有無參建構式,不能通過反射方式創建物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/226151.html
標籤:其他
