最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java開發(fā)中為什么要使用單例模式詳解

 更新時(shí)間:2021年06月01日 15:59:37   作者:鴨梨山大哎  
單例對(duì)于大家來說并不陌生,但是在什么時(shí)候用單例呢?為什么要用呢?本文就帶大家了解一下為什么要使用單例,文中有非常詳細(xì)的介紹,需要的朋友可以參考下

一、什么是單例模式?

單例設(shè)計(jì)模式(Singleton Design Pattern)理解起來非常簡單。一個(gè)類只允許創(chuàng)建一個(gè)對(duì)象(或者實(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 中。在 UserControllerOrderController 中,我們分別創(chuàng)建兩個(gè) Logger 對(duì)象。在 Web 容器的 Servlet 多線程環(huán)境下,如果兩個(gè) Servlet 線程同時(shí)分別執(zhí)行 login()create() 兩個(gè)函數(shù),并且同時(shí)寫日志到 log.txt 文件中,那就有可能存在日志信息互相覆蓋的情況。

為什么會(huì)出現(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è)對(duì)象級(jí)別的鎖,一個(gè)對(duì)象在不同的線程下同時(shí)調(diào)用 log() 函數(shù),會(huì)被強(qiáng)制要求順序執(zhí)行。但是,不同的對(duì)象之間并不共享同一把鎖。在不同的線程下,通過不同的對(duì)象調(diào)用執(zhí)行 log() 函數(shù),鎖并不會(huì)起作用,仍然有可能存在寫入日志互相覆蓋的問題。

在這里插入圖片描述

我這里稍微補(bǔ)充一下,在剛剛的講解和給出的代碼中,我故意“隱瞞”了一個(gè)事實(shí):我們給 log() 函數(shù)加不加對(duì)象級(jí)別的鎖,其實(shí)都沒有關(guān)系。因?yàn)?FileWriter 本身就是線程安全的,它的內(nèi)部實(shí)現(xiàn)中本身就加了對(duì)象級(jí)別的鎖,因此,在外層調(diào)用 write() 函數(shù)的時(shí)候,再加對(duì)象級(jí)別的鎖實(shí)際上是多此一舉。因?yàn)椴煌?Logger 對(duì)象不共享 FileWriter 對(duì)象,所以,FileWriter 對(duì)象級(jí)別的鎖也解決不了數(shù)據(jù)寫入互相覆蓋的問題。

那我們該怎么解決這個(gè)問題呢?實(shí)際上,要想解決這個(gè)問題也不難,我們只需要把對(duì)象級(jí)別的鎖,換成類級(jí)別的鎖就可以了。讓所有的對(duì)象都共享同一把鎖。這樣就避免了不同對(duì)象之間同時(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) { // 類級(jí)別的鎖
      writer.write(mesasge);
    }
  }
}

除了使用類級(jí)別鎖之外,實(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ù)雜。

相對(duì)于這兩種解決方案,單例模式的解決思路就簡單一些了。單例模式相對(duì)于之前類級(jí)別鎖的好處是,不用創(chuàng)建那么多 Logger 對(duì)象,一方面節(jié)省內(nèi)存空間,另一方面節(jié)省系統(tǒng)文件句柄(對(duì)于操作系統(tǒng)來說,文件句柄也是一種資源,不能隨便浪費(fèi))

我們將 Logger 設(shè)計(jì)成一個(gè)單例類,程序中只允許創(chuàng)建一個(gè) Logger 對(duì)象,所有的線程共享使用的這一個(gè) Logger 對(duì)象,共享一個(gè) FileWriter 對(duì)象,而 FileWriter 本身是對(duì)象級(jí)別線程安全的,也就避免了多線程情況下寫日志會(huì)互相覆蓋的問題。

按照這個(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)存之后,以對(duì)象的形式存在,也理所應(yīng)當(dāng)只有一份。再比如,唯一遞增 ID 號(hào)碼生成器,如果程序中有兩個(gè)對(duì)象,那就會(huì)存在生成重復(fù) ID 的情況,所以,我們應(yīng)該將 ID 生成器類設(shè)計(jì)為單例。

import java.util.concurrent.atomic.AtomicLong;
public class IdGenerator {
  // AtomicLong是一個(gè)Java并發(fā)庫中提供的一個(gè)原子變量類型,
  // 它將一些線程不安全需要加鎖的復(fù)合操作封裝為了線程安全的原子操作,
  // 比如下面會(huì)用到的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)文章

  • 淺析Java迭代器Iterator和Iterable的區(qū)別

    淺析Java迭代器Iterator和Iterable的區(qū)別

    Java語言中,Iterator和Iterable都是用來遍歷集合類數(shù)據(jù)結(jié)構(gòu)的接口,雖然它們有很多相似的地方,但在具體實(shí)現(xiàn)中卻有著一些不同之處,本文將詳細(xì)分析它們的區(qū)別,并提供相應(yīng)的代碼示例,需要的朋友可以參考下
    2023-07-07
  • 謹(jǐn)慎使用Java8的默認(rèn)方法

    謹(jǐn)慎使用Java8的默認(rèn)方法

    為什么要謹(jǐn)慎使用Java8的默認(rèn)方法?本文給出了為什么要慎用Java8默認(rèn)方法的原因,解釋的很詳細(xì),感興趣的朋友可以參考一下
    2016-01-01
  • Spring websocket并發(fā)發(fā)送消息異常的解決

    Spring websocket并發(fā)發(fā)送消息異常的解決

    本文主要介紹了 Spring websocket并發(fā)發(fā)送消息異常的解決,當(dāng)多個(gè)線程同時(shí)嘗試通過 WebSocket 會(huì)話發(fā)送消息時(shí),會(huì)拋出異常,下面就來解決一下,感興趣的可以了解一下
    2023-09-09
  • Java源碼解析ArrayList及ConcurrentModificationException

    Java源碼解析ArrayList及ConcurrentModificationException

    今天小編就為大家分享一篇關(guān)于Java源碼解析ArrayList及ConcurrentModificationException,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • 詳解Java創(chuàng)建多線程的四種方式以及優(yōu)缺點(diǎn)

    詳解Java創(chuàng)建多線程的四種方式以及優(yōu)缺點(diǎn)

    這篇文章主要介紹了Java創(chuàng)建多線程的四種方式以及優(yōu)缺點(diǎn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Netty中解碼器的作用及實(shí)現(xiàn)詳解

    Netty中解碼器的作用及實(shí)現(xiàn)詳解

    這篇文章主要介紹了Netty中解碼器的作用及實(shí)現(xiàn)詳解,本章我們只需要讓客戶端發(fā)送消息的時(shí)候循環(huán)發(fā)送100次,服務(wù)端不變,看看服務(wù)端是不是接收到了100條消息,需要的朋友可以參考下
    2023-12-12
  • java中List對(duì)象列表實(shí)現(xiàn)去重或取出及排序的方法

    java中List對(duì)象列表實(shí)現(xiàn)去重或取出及排序的方法

    這篇文章主要介紹了關(guān)于java中List對(duì)象列表實(shí)現(xiàn)去重或取出以及排序的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-08-08
  • 帶你走進(jìn)Maven的大門-最全Maven配置及集成idea工具總結(jié)

    帶你走進(jìn)Maven的大門-最全Maven配置及集成idea工具總結(jié)

    Maven項(xiàng)目對(duì)象模型(POM),是一個(gè)項(xiàng)目管理工具可以通過一小段描述信息來管理項(xiàng)目的構(gòu)建,報(bào)告和文檔的軟件.那我們想要在IDEA中使用Maven得進(jìn)行一些配置,接下來我們具體看一下是如何配置使用的,需要的朋友可以參考下
    2021-06-06
  • java實(shí)現(xiàn)猜字母游戲

    java實(shí)現(xiàn)猜字母游戲

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)猜字母小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-03-03
  • springboot連接不同數(shù)據(jù)庫的寫法詳解

    springboot連接不同數(shù)據(jù)庫的寫法詳解

    這篇文章主要介紹了springboot連接不同數(shù)據(jù)庫的寫法?,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04

最新評(píng)論

沂水县| 阳泉市| 乌什县| 射洪县| 抚州市| 泰顺县| 长治市| 安吉县| 甘谷县| 射阳县| 灵川县| 靖宇县| 上蔡县| 明星| 潮安县| 内黄县| 杭锦后旗| 旺苍县| 西畴县| 中卫市| 东阳市| 鄱阳县| 同江市| 闽侯县| 蒙山县| 民和| 梁平县| 阿荣旗| 全南县| 聊城市| 衡南县| 黎城县| 夏河县| 成安县| 礼泉县| 武冈市| 辽阳县| 康乐县| 蒲城县| 屯门区| 商城县|