Java開發(fā)中為什么要使用單例模式詳解
一、什么是單例模式?
單例設(shè)計(jì)模式(Singleton Design Pattern)理解起來非常簡單。一個(gè)類只允許創(chuàng)建一個(gè)對象(或者實(shí)例),那這個(gè)類就是一個(gè)單例類
,這種設(shè)計(jì)模式就叫作單例設(shè)計(jì)模式
,簡稱單例模式。
二、實(shí)戰(zhàn)案例一:處理資源訪問沖突
我們先來看第一個(gè)例子。在這個(gè)例子中,我們自定義實(shí)現(xiàn)了一個(gè)往文件中打印日志的 Logger 類
。具體的代碼實(shí)現(xiàn)如下所示:
public class Logger { private FileWriter writer; public Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); //true表示追加寫入 } public void log(String message) { writer.write(mesasge); } } // Logger類的應(yīng)用示例: public class UserController { private Logger logger = new Logger(); public void login(String username, String password) { // ...省略業(yè)務(wù)邏輯代碼... logger.log(username + " logined!"); } } public class OrderController { private Logger logger = new Logger(); public void create(OrderVo order) { // ...省略業(yè)務(wù)邏輯代碼... logger.log("Created an order: " + order.toString()); } }
看完代碼之后,先別著急看我下面的講解,你可以先思考一下,這段代碼存在什么問題。
在上面的代碼中,我們注意到,所有的日志都寫入到同一個(gè)文件 /Users/wangzheng/log.txt
中。在 UserController
和 OrderController
中,我們分別創(chuàng)建兩個(gè) Logger 對象。在 Web 容器的 Servlet 多線程
環(huán)境下,如果兩個(gè) Servlet
線程同時(shí)分別執(zhí)行 login()
和 create()
兩個(gè)函數(shù),并且同時(shí)寫日志到 log.txt
文件中,那就有可能存在日志信息互相覆蓋的情況。
為什么會出現(xiàn)互相覆蓋呢?
我們可以這么類比著理解。在多線程環(huán)境下,如果兩個(gè)線程同時(shí)給同一個(gè)共享變量加 1,因?yàn)楣蚕碜兞渴歉偁庂Y源,所以,共享變量最后的結(jié)果有可能并不是加了 2,而是只加了 1。同理,這里的 log.txt 文件也是競爭資源,兩個(gè)線程同時(shí)往里面寫數(shù)據(jù),就有可能存在互相覆蓋的情況
。
那如何來解決這個(gè)問題呢?我們最先想到的就是通過加鎖的方式:給 log() 函數(shù)
加互斥鎖(Java 中可以通過 synchronized 的關(guān)鍵字),同一時(shí)刻只允許一個(gè)線程調(diào)用執(zhí)行 log() 函數(shù)。具體的代碼實(shí)現(xiàn)如下所示:
public class Logger { private FileWriter writer; public Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); //true表示追加寫入 } public void log(String message) { synchronized(this) { writer.write(mesasge); } } }
不過,你仔細(xì)想想,這真的能解決多線程寫入日志時(shí)互相覆蓋的問題嗎?答案是否定的。這是因?yàn)?,這種鎖是一個(gè)對象級別的鎖,一個(gè)對象在不同的線程下同時(shí)調(diào)用 log() 函數(shù),會被強(qiáng)制要求順序執(zhí)行。但是,不同的對象之間并不共享同一把鎖
。在不同的線程下,通過不同的對象調(diào)用執(zhí)行 log() 函數(shù),鎖并不會起作用,仍然有可能存在寫入日志互相覆蓋的問題。
我這里稍微補(bǔ)充一下,在剛剛的講解和給出的代碼中,我故意“隱瞞”了一個(gè)事實(shí):我們給 log() 函數(shù)
加不加對象級別的鎖,其實(shí)都沒有關(guān)系。因?yàn)?FileWriter
本身就是線程安全的,它的內(nèi)部實(shí)現(xiàn)中本身就加了對象級別的鎖,因此,在外層調(diào)用 write() 函數(shù)的時(shí)候,再加對象級別的鎖實(shí)際上是多此一舉。因?yàn)椴煌?Logger 對象不共享 FileWriter 對象,所以,FileWriter 對象級別的鎖也解決不了數(shù)據(jù)寫入互相覆蓋的問題
。
那我們該怎么解決這個(gè)問題呢?實(shí)際上,要想解決這個(gè)問題也不難,我們只需要把對象級別的鎖,換成類級別的鎖
就可以了。讓所有的對象都共享同一把鎖。這樣就避免了不同對象之間同時(shí)調(diào)用 log() 函數(shù),而導(dǎo)致的日志覆蓋問題。具體的代碼實(shí)現(xiàn)如下所示:
public class Logger { private FileWriter writer; public Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); //true表示追加寫入 } public void log(String message) { synchronized(Logger.class) { // 類級別的鎖 writer.write(mesasge); } } }
除了使用類級別鎖之外,實(shí)際上,解決資源競爭問題的辦法還有很多,分布式鎖是最常聽到的一種解決方案。不過,實(shí)現(xiàn)一個(gè)安全可靠、無 bug、高性能的分布式鎖,并不是件容易的事情
。除此之外,并發(fā)隊(duì)列(比如 Java 中的 BlockingQueue)也可以解決這個(gè)問題:多個(gè)線程同時(shí)往并發(fā)隊(duì)列里寫日志,一個(gè)單獨(dú)的線程負(fù)責(zé)將并發(fā)隊(duì)列中的數(shù)據(jù),寫入到日志文件。這種方式實(shí)現(xiàn)起來也稍微有點(diǎn)復(fù)雜。
相對于這兩種解決方案,單例模式的解決思路就簡單一些了。單例模式相對于之前類級別鎖的好處是,不用創(chuàng)建那么多 Logger 對象,一方面節(jié)省內(nèi)存空間,另一方面節(jié)省系統(tǒng)文件句柄
(對于操作系統(tǒng)來說,文件句柄也是一種資源,不能隨便浪費(fèi))
我們將 Logger
設(shè)計(jì)成一個(gè)單例類,程序中只允許創(chuàng)建一個(gè) Logger 對象
,所有的線程共享使用的這一個(gè) Logger 對象
,共享一個(gè) FileWriter 對象
,而 FileWriter
本身是對象級別線程安全的,也就避免了多線程情況下寫日志會互相覆蓋的問題。
按照這個(gè)設(shè)計(jì)思路,我們實(shí)現(xiàn)了 Logger 單例類
。具體代碼如下所示:
public class Logger { private FileWriter writer; private static final Logger instance = new Logger(); private Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); //true表示追加寫入 } public static Logger getInstance() { return instance; } public void log(String message) { writer.write(mesasge); } } // Logger類的應(yīng)用示例: public class UserController { public void login(String username, String password) { // ...省略業(yè)務(wù)邏輯代碼... Logger.getInstance().log(username + " logined!"); } } public class OrderController { public void create(OrderVo order) { // ...省略業(yè)務(wù)邏輯代碼... Logger.getInstance().log("Created a order: " + order.toString()); } }
三、實(shí)戰(zhàn)案例二:表示全局唯一類
從業(yè)務(wù)概念上,如果有些數(shù)據(jù)在系統(tǒng)中只應(yīng)保存一份,那就比較適合設(shè)計(jì)為單例類。比如,配置信息類。在系統(tǒng)中,我們只有一個(gè)配置文件,當(dāng)配置文件被加載到內(nèi)存之后,以對象的形式存在,也理所應(yīng)當(dāng)只有一份。再比如,唯一遞增 ID 號碼生成器
,如果程序中有兩個(gè)對象,那就會存在生成重復(fù) ID 的情況,所以,我們應(yīng)該將 ID 生成器類
設(shè)計(jì)為單例。
import java.util.concurrent.atomic.AtomicLong; public class IdGenerator { // AtomicLong是一個(gè)Java并發(fā)庫中提供的一個(gè)原子變量類型, // 它將一些線程不安全需要加鎖的復(fù)合操作封裝為了線程安全的原子操作, // 比如下面會用到的incrementAndGet(). private AtomicLong id = new AtomicLong(0); private static final IdGenerator instance = new IdGenerator(); private IdGenerator() {} public static IdGenerator getInstance() { return instance; } public long getId() { return id.incrementAndGet(); } } // IdGenerator使用舉例 long id = IdGenerator.getInstance().getId();
到此這篇關(guān)于Java開發(fā)中為什么要使用單例模式詳解的文章就介紹到這了,更多相關(guān)Java單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC實(shí)現(xiàn)自定義類型轉(zhuǎn)換器
本篇文章主要介紹了SpringMVC實(shí)現(xiàn)自定義類型轉(zhuǎn)換器 ,詳細(xì)的介紹了自定義類型轉(zhuǎn)換器的用法和好處,有興趣的可以了解一下。2017-04-04Java?Spring的核心與設(shè)計(jì)思想你知道嗎
這篇文章主要為大家詳細(xì)介紹了Java?Spring的核心與設(shè)計(jì)思想,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03JavaFX實(shí)現(xiàn)UI美觀效果代碼實(shí)例
這篇文章主要介紹了JavaFX實(shí)現(xiàn)UI美觀效果代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07Kafka是什么及如何使用SpringBoot對接Kafka(最新推薦)
這篇文章主要介紹了Kafka是什么,以及如何使用SpringBoot對接Kafka,今天我們通過一個(gè)Demo講解了在SpringBoot中如何對接Kafka,也介紹了下關(guān)鍵類?KafkaTemplate,需要的朋友可以參考下2023-11-11SpringBoot中集成Swagger2及簡單實(shí)用
使用Swagger你只需要按照它的規(guī)范去定義接口及接口相關(guān)的信息,再通過Swagger衍生出來的一系列項(xiàng)目和工具,就可以做到生成各種格式的接口文檔,以及在線接口調(diào)試頁面等等,這篇文章主要介紹了SpringBoot中集成Swagger2,需要的朋友可以參考下2023-06-06JDBC連接Mysql的5種方式實(shí)例總結(jié)
JDBC是Java DataBase Connectivity技術(shù)的簡稱,是一種可用于執(zhí)行 SQL語句的Java API,下面這篇文章主要給大家介紹了關(guān)于JDBC連接Mysql的5種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04如何自定義hibernate validation注解示例代碼
Hibernate Validator 是 Bean Validation 的參考實(shí)現(xiàn) . Hibernate Validator 提供了 JSR 303 規(guī)范中所有內(nèi)置 constraint 的實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于如何自定義hibernate validation注解的相關(guān)資料,需要的朋友可以參考下2018-04-04