java鎖機(jī)制

實(shí)戰(zhàn)才是硬道理,說(shuō)教有些虛,不玩虛的。先讓我們看一個(gè)例子:創(chuàng)建一個(gè)線程類,實(shí)現(xiàn)在控制臺(tái)打印數(shù)字從0到9999。然后同時(shí)開啟10個(gè)線程,查看打印結(jié)果;

程序清單1-1

程序清單1-1,其實(shí)就是讓10個(gè)線程在控制臺(tái)上數(shù)數(shù),從0數(shù)到9999。理想情況下,我們希望看到一個(gè)線程數(shù)完,然后另一個(gè)線程才開始數(shù)。但是控制臺(tái)打印的順序告訴我們,這10個(gè)線程是亂糟糟的在那里搶著數(shù),絲毫沒有任何規(guī)矩可言。

如何才能在理想情況下每個(gè)線程依次打印數(shù)字呢?引入了多線程編程技術(shù)。

Java多線程編程中,關(guān)鍵字synchronized是繞不開的。但是很多人看到這個(gè)東西會(huì)感到困惑:“都說(shuō)同步機(jī)制是通過(guò)對(duì)象鎖來(lái)實(shí)現(xiàn)的,但是這么一個(gè)關(guān)鍵字,我也看不出來(lái)Java程序鎖住了哪個(gè)對(duì)象啊?”讓我們使用synchronized關(guān)鍵字來(lái)修飾run()方法看看效果如何。

程序清單1-2

執(zhí)行程序,查看控制臺(tái)打印順序,打印順序依然亂糟糟的,程序清單1-1與程序清單1-2并沒有區(qū)別?!拔乙呀?jīng)使用synchronized來(lái)同步run()方法了啊, 哪里出錯(cuò)了呢?”

我們?cè)僭囍薷南鲁绦颍瑂ynchronized同步run()方法中的代碼塊。

程序清單1-3

執(zhí)行程序,查看控制臺(tái)打印順序,我們看到了預(yù)期的效果:10個(gè)線程不再是爭(zhēng)先恐后的報(bào)數(shù)了,而是一個(gè)接一個(gè)的報(bào)數(shù)。對(duì)比程序清單1-3跟1-2,程序清單1-3在main方法啟動(dòng)10個(gè)線程之前,創(chuàng)建了一個(gè)String類型的對(duì)象。并通過(guò)ThreadTest的構(gòu)造函數(shù),將這個(gè)對(duì)象賦值給每一個(gè)ThreadTest線程對(duì)象中的私有變量lock。根據(jù)Java方法的傳值特點(diǎn),我們知道,這些線程的lock變量實(shí)際上指向的是堆內(nèi)存中的同一個(gè)內(nèi)存區(qū)域,即存放main函數(shù)中的lock變量的區(qū)域。程序清單1-2,對(duì)于一個(gè)成員方法加synchronized關(guān)鍵字,這實(shí)際上是以這個(gè)成員方法所在的對(duì)象本身作為對(duì)象鎖的,創(chuàng)建多少個(gè)對(duì)象就有多少個(gè)對(duì)象鎖,每個(gè)對(duì)象鎖只對(duì)該對(duì)象起作用。一共十個(gè)線程,每個(gè)線程持有自己線程對(duì)象的那個(gè)對(duì)象鎖。這必然不能產(chǎn)生同步的效果。換句話說(shuō),如果要對(duì)這些線程進(jìn)行同步,那么這些線程所持有的對(duì)象鎖應(yīng)當(dāng)是共享且唯一的!

>>對(duì)象鎖

可以這樣理解,每個(gè)實(shí)例對(duì)象僅有一把鎖,把對(duì)象看作一間房子,哪個(gè)線程先拿到鎖就進(jìn)入房間中,其他線程在門外等待房間中線程出來(lái)才能進(jìn)入房間。

對(duì)象鎖又稱之為內(nèi)置鎖。除了對(duì)象鎖還有類鎖。

>>類鎖

程序中通過(guò)類可以創(chuàng)建多個(gè)實(shí)例對(duì)象,但每個(gè)類只有一個(gè)類實(shí)例,JVM類加載完成,每個(gè)類在方法區(qū)只存在一份,方法區(qū)是內(nèi)存共享的。所以類鎖只有一個(gè)。

使用類鎖來(lái)實(shí)現(xiàn)程序

程序清單1-4

通過(guò)程序清單1-2,我們清楚的了解到對(duì)于一個(gè)成員方法加synchronized關(guān)鍵字,這實(shí)際上是以這個(gè)成員方法所在的對(duì)象本身作為對(duì)象鎖。在本例中,我們以ThreadTest類實(shí)例作為鎖,全局唯一。

我們以另一種類鎖的方式來(lái)實(shí)現(xiàn)程序

程序清單1-5

你們應(yīng)該很困惑:這里synchronized靜態(tài)方法是用什么來(lái)做對(duì)象鎖的呢?

我們知道,對(duì)于同步靜態(tài)方法,對(duì)象鎖就是該靜態(tài)方法所在的類的Class實(shí)例,由于在JVM中,所有被加載的類都有唯一的類實(shí)例,具體到本例,就是唯一的 ThreadTest3.class對(duì)象。不管我們創(chuàng)建了該類的多少實(shí)例,但是它的類實(shí)例仍然是一個(gè)!

這樣我們就知道了:

1、對(duì)于同步的方法或者代碼塊來(lái)說(shuō),必須獲得對(duì)象鎖才能夠進(jìn)入同步方法或者代碼塊進(jìn)行操作;

2、如果采用函數(shù)或方法級(jí)別的同步,則對(duì)象鎖即為函數(shù)或方法所在的對(duì)象,如果是靜態(tài)方法,對(duì)象鎖即指 函數(shù)哦或方法所在的Class對(duì)象(唯一);

3、對(duì)于代碼塊,synchronized(abc)中的內(nèi)置鎖即為abc對(duì)象鎖;

4、因?yàn)榈谝环N情況,對(duì)象鎖即為每一個(gè)線程對(duì)象,因此有多個(gè),所以同步失效,第二種共用同一個(gè)對(duì)象lock鎖,因此同步生效,第三個(gè)因?yàn)槭峭届o態(tài)方法,因此鎖住的是ThreadTest3.class,因此同步生效。

如上述正確,則同步有兩種方式,同步代碼塊和同步函數(shù)或方法(為什么沒有wait和notify?這個(gè)我會(huì)在補(bǔ)充章節(jié)中做出闡述)

如果是同步代碼塊,則鎖住的對(duì)象需要編程人員自己指定,一般有些代碼為synchronized(this)只有在單態(tài)模式才生效(類的實(shí)例有任何時(shí)候僅有一個(gè));

如果是同步函數(shù)或方法,則分靜態(tài)和非靜態(tài)兩種。

靜態(tài)方法則一定會(huì)同步,非靜態(tài)方法在單例模式才生效,推薦用靜態(tài)方法(不用擔(dān)心是否單例)。

所以說(shuō),在Java多線程編程中,關(guān)鍵字synchronized最常見的用法是依靠對(duì)象鎖的機(jī)制來(lái)實(shí)現(xiàn)線程同步的。

JVM邏輯內(nèi)存模型

JVM邏輯內(nèi)存模型
虛擬機(jī)內(nèi)存模型中定義的訪問(wèn)操作

寄存器:這是最快的存儲(chǔ)區(qū),因?yàn)樗挥诓煌谄渌鎯?chǔ)區(qū)的地方--處理器內(nèi)部。但是寄存器的數(shù)量機(jī)器有限,所以寄存器根據(jù)需求進(jìn)行分配。你不能直接控制,也不能在程序中直接感覺到它的存在任何跡象。

堆棧:位于通用RAM(隨機(jī)訪問(wèn)存儲(chǔ)器)中,但通過(guò)“堆棧指針”可以從處理器那里獲得直接支持。堆棧指針若向下移動(dòng),則分配新的內(nèi)存;若向上移動(dòng),則釋放那些內(nèi)存。這是一種快速有效的分配存儲(chǔ)方法,僅次于寄存器。創(chuàng)建程序時(shí),Java系統(tǒng)必須知道存儲(chǔ)在堆棧內(nèi)的所有項(xiàng)的確切生命周期,以便上下移動(dòng)堆棧指針。這一約束限制了程序的靈活性,所以雖然某些Java數(shù)據(jù)存儲(chǔ)與堆棧中----特別是對(duì)象引用,但是Java對(duì)象卻不存在堆棧中;

:一種通用的內(nèi)存池(位于RAM區(qū)),用于存放所有的Java對(duì)象。堆不同于堆棧的好處是:編譯器不需要知道存儲(chǔ)的數(shù)據(jù)在堆中存活多長(zhǎng)時(shí)間。因此,堆里分配對(duì)象有很大的靈活性。當(dāng)需要一個(gè)對(duì)象時(shí),只需要new寫一行簡(jiǎn)單的代碼,當(dāng)執(zhí)行這段代碼時(shí),會(huì)自動(dòng)在堆里進(jìn)行存儲(chǔ)分配。當(dāng)然,為這種靈活性要付出代價(jià):用堆進(jìn)行內(nèi)存分配和清理可能比用堆棧進(jìn)行內(nèi)存分配和清理需要更長(zhǎng)的時(shí)間。

常量存儲(chǔ):常量直接存放在程序代碼內(nèi)部,這樣做是安全的,因?yàn)樗麄冇肋h(yuǎn)不會(huì)被改變。有時(shí)候,在嵌入式系統(tǒng)中,常量本身會(huì)和其他部分分離開,在這種情況下,常量可以存放在ROM(只讀存儲(chǔ)器)中。

非RAM存儲(chǔ):如果數(shù)據(jù)完全存活在程序之外,那么它可以完全不受程序的任何控制,在程序沒有運(yùn)行時(shí)也可以存在。比較基本的兩個(gè)例子是“流對(duì)象”和“持久化對(duì)象”。流對(duì)象中,對(duì)象轉(zhuǎn)換成字節(jié)流,通暢被發(fā)送到另一臺(tái)機(jī)器。在“持久化”對(duì)象中,對(duì)象被存在磁盤上,因此,及時(shí)程序終止,數(shù)據(jù)也可以保持自己的狀態(tài)。這種存儲(chǔ)的技巧在于:把對(duì)象轉(zhuǎn)換成可以在其他媒介上存儲(chǔ)的事務(wù),在需要的時(shí)候,可以恢復(fù)成常規(guī)的,基于RAM的對(duì)象。比如Java中輕量級(jí)的JDBC和Hibernate這樣的機(jī)制提供支持。

Java創(chuàng)建對(duì)象的存儲(chǔ)方式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,740評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,818評(píng)論 11 349
  • (一)Java部分 1、列舉出JAVA中6個(gè)比較常用的包【天威誠(chéng)信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,265評(píng)論 0 62
  • 很多人都知道,在Java多線程編程中,有一個(gè)重要的關(guān)鍵字Synchronized。但是很多人看到這個(gè)東西會(huì)感到困惑...
    Ten_Minutes閱讀 502評(píng)論 0 2
  • 作為一只大一狗,在一次出演某個(gè)晚會(huì)時(shí),在后臺(tái)化妝,看到其他妹紙分分鐘各種眼線眼影,不禁發(fā)現(xiàn)我除了會(huì)畫個(gè)眉毛涂個(gè)睫毛...
    千焦每摩爾閱讀 627評(píng)論 1 3

友情鏈接更多精彩內(nèi)容