目錄
單例模式
單例模式(一個類模板,在整個系統執行過程中,只允許產生一個實例)應用廣泛,主要應用在:
- 配置文件
- Ioc容器
- 日曆
- 工廠本身
單例模式:解決一個併發訪問的時候線程安全問題,保證單例的技術方案有很多種
餓漢式單例
在實例使用之前,不管你用不用,我都先new出來再說,避免了線程安全問題
餓漢式單例:
public class Hungry {
//私有構造方法,防止外部new
private Hungry(){}
private static final Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
我們來測試:
public static void main(String[] args) {
int count = 200;
//發令槍,我就能想到運動員
final CountDownLatch latch = new CountDownLatch(count);
long start = System.currentTimeMillis();
for (int i = 0; i < count;i ++) {
new Thread(){
@Override
public void run() {
try{
try {
// 阻塞
// count = 0 就會釋放所有的共享鎖
// 萬箭齊發
latch.await();
}catch(Exception e){
e.printStackTrace();
}
//必然會調用,可能會有很多線程同時去訪問getInstance()
Object obj = Hungry.getInstance();
System.out.println(System.currentTimeMillis() + ":" + obj);
}catch (Exception e){
e.printStackTrace();
}
}
}.start(); //每循環一次,就啓動一個線程,具有一定的隨機性
//每次啓動一個線程,count --
latch.countDown();
}
long end = System.currentTimeMillis();
System.out.println("總耗時:" + (end - start));
}
打印結果發現,無論怎麼運行,程序始終拿到的是一個實例
懶漢式單例
默認加載的時候不實例化,在需要用到這個實例的時候進行實例化(延時加載)
懶漢式單例:
public class LazyOne {
private LazyOne(){}
//靜態塊,公共內存區域
private static LazyOne lazy = null;
public static LazyOne getInstance(){
//調用方法之前,先判斷
//如果沒有初始化,將其進行初始化,並且賦值
//將該實例緩存好
if(lazy == null){
//兩個線程都會進入這個if裏面
lazy = new LazyOne();
}
//如果已經初始化,直接返回之前已經保存好的結果
return lazy;
}
}
打印結果發現存在不同實例,說明餓漢式單例是線程不安全!
怎麼變爲安全的呢!我們通常可以這樣設計餓漢式單例(在獲取實例方法上加上synchronized 鎖)
public class LazyTwo {
private LazyTwo(){}
private static LazyTwo lazy = null;
public static synchronized LazyTwo getInstance(){
if(lazy == null){
lazy = new LazyTwo();
}
return lazy;
}
}
然後我們將測試工具類實例改爲:
運行發現,無論怎麼運行,程序始終拿到的是一個實例,說明synchronized 鎖是可以保證線程安全的!
但是synchronized 性能並不是最好的鎖!
我們看這樣的測試:
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 200000000;i ++) {
Object obj = LazyTwo.getInstance();
}
long end = System.currentTimeMillis();
System.out.println("總耗時:" + (end - start));
}
運行發現每次獲取的時間性能較低,由此產生另一種單例,內部類單例模式
內部類單例
特點:
- 在外部類被調用的時候內部類纔會被加載
- 內部類一定是要在方法調用之前初始化
- 巧妙地避免了線程安全問題
- 這種形式兼顧餓漢式的內存浪費,也兼顧synchronized性能問題,完美地屏蔽了這兩個缺點
// 史上最牛B的單例模式的實現方式
public class LazyThree {
private boolean initialized = false;
//每一個關鍵字都不是多餘的
//static 是爲了使單例的空間共享
//保證這個方法不會被重寫,重載
public static final LazyThree getInstance(){
//在返回結果以前,一定會先加載內部類
return LazyHolder.LAZY;
}
//默認不加載
private static class LazyHolder{
private static final LazyThree LAZY = new LazyThree();
}
}
註冊登記式單例
- 每使用一次,都往一個固定的容器中去註冊並且將使用過的對象進行緩存,下次取對象就直接從緩存中取值,以保證每次獲取的都是同一個對象
- IOC中的單例模式,就是典型的註冊登記式單例
基於map形式的註冊單例:
public class RegisterMap {
private RegisterMap(){}
private static Map<String,Object> register = new ConcurrentHashMap<String,Object>();
public static RegisterMap getInstance(String name){
if(name == null){
name = RegisterMap.class.getName();
}
if(register.get(name) == null){
try {
register.put(name, new RegisterMap());
}catch(Exception e){
e.printStackTrace();
}
}
return (RegisterMap)register.get(name);
}
}
枚舉式單例
使用常量值來保證對象的唯一,實際上就是註冊登記式的一種
public enum DataSourceEnum {
DATASOURCE;
private DBConnection connection = null;
private DataSourceEnum() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}