上機考試
阿新 • • 發佈:2020-12-14
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。
注意:
- 1、單例類只能有一個例項。
- 2、單例類必須自己建立自己的唯一例項。
- 3、單例類必須給所有其他物件提供這一例項。
上面這幾句引自https://www.runoob.com/design-pattern/singleton-pattern.html
眾所周知,單例模式分為懶漢式和餓漢式。下面我逐一介紹。
public class HungryMan {
private HungryMan(){}
private static final HungryMan HUNGRY_MAN = new HungryMan();
public static HungryMan getInstance(){
return HUNGRY_MAN;
}
public static void main(String[] args) {
HungryMan hungryMan1 = HungryMan.getInstance();
HungryMan hungryMan2 = HungryMan.getInstance();
System.out.println(hungryMan1.hashCode());
System.out.println(hungryMan2.hashCode());
}
}
說明建立的是同一個物件
但是餓漢式會造成資源的浪費 懶漢式:
public class LazyMan {
private LazyMan(){}
public static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
return lazyMan;
}
return lazyMan;
}
public static void main(String[] args) {
LazyMan lazyMan1 = LazyMan.getInstance();
LazyMan lazyMan2 = LazyMan.getInstance();
System.out.println(lazyMan1.hashCode());
System.out.println(lazyMan2.hashCode());
}
}
看到兩個物件是一樣的
但是多執行緒的情況下懶漢式是不安全的
檢驗不安全:
public class LazyMan {
private LazyMan(){}
public static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
//睡一會
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
lazyMan = new LazyMan();
return lazyMan;
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
LazyMan instance = LazyMan.getInstance();
System.out.println(instance.hashCode());
}).start();
}
}
}
雜湊值是不一樣的,所以懶漢式在併發的情況下是不安全的
加了鎖的懶漢式
public class LazyMan {
private LazyMan(){}
public static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized(LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
return lazyMan;
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
LazyMan instance = LazyMan.getInstance();
System.out.println(instance.hashCode());
}).start();
}
}
}
延遲一秒的結果(程式碼中沒有體現)
執行緒A執行到3的位置,執行緒B搶到執行權,停留在1的位置,因為有鎖不能繼續執行,執行緒A建立物件,執行緒B即使能執行到2的位置,但是因為物件不是null,還是不能執行到3的位置,不能建立新的物件。
但是,另一個問題來了
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized(LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
/**
* lazyMan = new LazyMan();
* 但是這句話並不是一個原子性的,它有三個步驟
* 1、開闢一段記憶體地址
* 2、執行建構函式,初始化物件
* 3、將物件指向這段記憶體地址
* 完美的情況下是 1 2 3 ,但是可能 萬一是 1 3 2
* 開闢完記憶體地址,指向這段記憶體地址,初始化物件
* 此時的初始化物件已經無濟於事,因為已經指向記憶體地址了
* 這就導致,不是原子性的問題帶來的影響
*/
}
}
return lazyMan;
}
return lazyMan;
}
所以我們需要加上volatile
但是我們依舊可以通過反射來破壞
package com.xiaofei.single;
import java.lang.reflect.Constructor;
import java.util.concurrent.TimeUnit;
/**
* @author xiaofei
* @version 1.0
* @date 2020/9/12 18:47
*/
public class LazyMan {
private LazyMan(){}
public volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized(LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
return lazyMan;
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
LazyMan lazyMan1 = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1.hashCode());
System.out.println(lazyMan2.hashCode());
}
}
可以發現,反射依舊可以破壞
下面我們用到終極辦法:列舉
public enum SingleEnum {
SINGLENUM;
}
class Test{
public static void main(String[] args) {
SingleEnum singlenum1 = SingleEnum.SINGLENUM;
SingleEnum singlenum2 = SingleEnum.SINGLENUM;
System.out.println(singlenum1.hashCode());
System.out.println(singlenum2.hashCode());
}
}
列舉寫法簡單,而且不會被反射破壞