23大設計模式—01單例模式
簡介:單例模式就是保證類只有一個實例,並且提供一個可以訪問到該單例對象的全局訪問點
下面舉幾個使用到單例模式的例子:
Windows的Task Manager(任務管理器)就是很典型的單例模式
項目中,讀取配置文件的類,一般也只有一個對象。沒有必要每次使用配置文件數據,每次new一個對象去讀取。
數據庫連接池的設計一般也是採用單例模式,因爲數據庫連接是一種數據庫資源
在Spring中,每個Bean默認就是單例的,這樣做的優點是Spring容器可以管理
在servlet編程中,每個Servlet也是單例
在spring MVC框架/struts1框架中,控制器對象也是單例
上面的例子瞭解即可,當你用到時自然會理解
單例模式一般有5種常見實現方式
一.五大實現方式
1.餓漢式
public class SingleTon {
//第一步,定義類對象,對象的修飾符爲 private static final(當然可以不要final),
//這樣寫保證了在JVM加載字節碼文件進內存初始化時就可以產生對象,沒有延時
private static SingleTon singleTon = new SingleTon();
//第二步,定義空參的構造方法,修飾符爲private
private SingleTon(){
}
//第三步,定義方法返回單例對象,修飾符爲public static
public static SingleTon getInstance(){
return singleTon;
}
}
觀看上面的代碼發現:除了單例對象沒有其它的成員,因此不會存在數據共享,也就沒有了線程安全問題,同時由於線程安全,因此沒有使用線程同步技術,那麼調用的效率也高
總結:線程安全,調用效率高,但是沒有延時加載
2.懶漢式
public class SingleTon {
//第一步,定義類對象,對象的修飾符爲 private static
private static SingleTon singleTon;
//第二步,定義空參的構造方法,修飾符爲private
private SingleTon(){
}
//第三步,定義方法返回單例對象,修飾符爲public static synchronized防止線程安全
public static synchronized SingleTon getInstance(){
if(singleTon==null)
{
singleTon = new SingleTon();
}
return singleTon;
}
}
總結:線程安全,調用效率不高(因爲有同步)。 但是,可以延時加載,那麼資源的利用率高了(因爲到要用的時候纔去創建,就不會像餓漢式那樣一上來就創建單例對象,卻有可能這個對象一次沒用)。
如果你對多線程不太瞭解可以看我的這篇文章:多線程知識複習鞏固
注意懶漢模式與餓漢模式代碼上的區別。
建議如果對象用的頻繁就用餓漢式,否則用懶漢式
一般掌握了上面的兩種方式就足夠了,下面3種是擴展的
3.雙重檢測鎖實現
由於JVM底層內部模型原因,偶爾會出問題。不建議使用,而且工作根本不會用到,瞭解即可(這個代碼我也不會,都是在網上找的,是真的不重要)
public class SingletonDemo3 {
private static SingletonDemo3 instance = null;
public static SingletonDemo3 getInstance() {
if (instance == null) {
SingletonDemo3 sc;
synchronized (SingletonDemo3.class) {
sc = instance;
if (sc == null) {
synchronized (SingletonDemo3.class) {
if(sc == null) {
sc = new SingletonDemo3();
}
}
instance = sc;
}
}
}
return instance;
}
private SingletonDemo3(){
}
}
4.靜態內部類實現方式
先複習一個java基礎的知識點
1.外部類初次加載,會初始化靜態變量、靜態代碼塊、靜態方法,但不會加載內部類和靜態內部類。
2.實例化外部類,調用外部類的靜態方法、靜態變量,則外部類必須先進行加載,但只加載一次。
3.直接調用靜態內部類時,外部類不會加載。
更多詳細內容請學習JVM的高階知識
public class SingleTon {
//定義靜態內部類,裏面賦予單例對象
private static class SingletonClassInner{
private static final SingleTon instance = new SingleTon();
}
//通過該函數獲取單例對象,只有在調用該方法時纔會初始化單例對象,這是一種懶加載機制
public static SingleTon getInstance(){
return SingletonClassInner.instance;
}
private SingleTon(){
}
}
上面的加載內部類機制告訴我們線程是安全的(Java的類加載機制是線程安全的),而final關鍵字也是保證單例的一個方式
總結:由於沒有使用synchronized因此不存在延時加載的問題,那麼他是高效的,同時線程安全且可延時加載
5.使用枚舉的實現方式
public enum Single {
//枚舉元素本身就是一個單例的對象並且不需要擔心反射與反序列化帶來的問題
INSTANCE;
//可以定義方法來添加操作
public void SingleOperation(){
}
}
總結:線程安全,調用效率高,不能延時加載且不需要擔心反射與反序列化帶來的問題
二.破解單例模式及解決方案
不過還是有問題,上面前四種方法中才在以下問題:
①反射可以破解上面幾種(不包含枚舉式)實現方式!
可以在構造方法中手動拋出異常控制
例子:
使用靜態內部類的方式來演示
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<SingleTon> singleClass = SingleTon.class;
Constructor<SingleTon> c = singleClass.getDeclaredConstructor(null);
c.setAccessible(true);
SingleTon single1 = c.newInstance();
SingleTon single2 = c.newInstance();
System.out.println(single1);
System.out.println(single2);
}
輸出如下:
SingleTon@2503dbd3
SingleTon@4b67cf4d
解決方案:在構造方法中拋出異常即可
public class SingleTon {
private static class SingleClass{
private static final SingleTon instance = new SingleTon();
}
private SingleTon(){
if(SingleClass.instance!=null){
throw new RuntimeException();
}
}
public static SingleTon getInstance(){
return SingleClass.instance;
}
}
②反序列化可以破解上面幾種((不包含枚舉式))實現方式!
可以通過定義readResolve()防止獲得不同對象。 反序列化時,如果對象所在類定義了readResolve(),(實際是一種回調),定義返回哪個對象
使用靜態內部類的方式來演示,注意去實現Serializable
public static void main(String[] args) throws IOException, ClassNotFoundException {
SingleTon s1 = SingleTon.getInstance();
SingleTon s2 = SingleTon.getInstance();
System.out.println(s1==s2);
//通過序列化來破解
FileOutputStream fileOutputStream = new FileOutputStream("D:\\a.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(s1);
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream("D:\\a.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
SingleTon s3=(SingleTon) objectInputStream.readObject();
System.out.println(s3==s1);
}
輸出:
true
false
解決方案:
添加readResolve方法
import java.io.Serializable;
public class SingleTon implements Serializable {
private static class SingleClass{
private static final SingleTon instance = new SingleTon();
}
private SingleTon(){
if(SingleClass.instance!=null){
throw new RuntimeException();
}
}
public static SingleTon getInstance(){
return SingleClass.instance;
}
//反序列化時,如果定義了readResolve()則直接返回此方法指定的對象。而不需要單獨再創建新對象!
public Object readResolve(){
return SingleClass.instance;
}
}
三.效率測試
這裏給出一個測試靜態內部類例子,其他類似:
import org.junit.Test;
import java.io.*;
import java.util.concurrent.CountDownLatch;
public class AVL {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(10);
final int nums = 10;
for(int i=0;i<nums;i++){
new Thread(){
@Override
public void run() {
SingleTon.getInstance();
countDownLatch.countDown();
}
}.start();
}
countDownLatch.await();
System.out.println(System.currentTimeMillis()-start);
}
}
結果:
上一篇:前言
下一篇:簡單工廠模式