<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 17 資源有限,請排隊等候—Synchronized使用、原理及缺陷 > 人生的旅途,前途很遠,也很暗。然而不要怕,不怕的人的面前才有路。 > ——?魯?迅 到現在為止,本專欄已經發布了近一半的內容。我還記得早在第三節就有同學留言預測下一節要講synchronized。確實,很多講解Java并發編程的書籍會比較早的安排講解 synchronized 關鍵字。其實synchronized 使用起來非常簡單,并且幾乎不用做什么思考,需要確保線程安全的部分直接用就好了。所以我覺得只是使用的話沒什么好講的,隨著其它內容一并介紹下就可以了。在介紹了并發的三大特性,順便學習完Atomic 和 volatile 后,再深入學習 synchronized。我認為這樣的安排更能幫助讀者理解。 ## 1、synchronized 的作用 前兩節我們學習了能保證原子性的 Atomic 變量以及保證可見性和有序性的 volatile 關鍵字。這兩種方式是輕量級的同步方式,不過存在其局限性,前面已經做過總結。那么“重量級”的同步如何做呢?synchronized 代碼塊就是一種實現方式。在 synchronized 代碼塊中的代碼在多線程中會同步執行,同步執行的意思就是——排隊。這就像我們去體檢,每個人可以并行從家里到醫院,并行拿表、填表,并行走到各個檢查室門口。但是,一旦要做檢查了,我們就需要在檢查室門口排隊。這是因為只有一個大夫做檢查,大夫是共享資源。對共享資源的訪問我們要保證同步,否則就會出現問題。 synchronized 作用域中的代碼為同步執行的,也就是并發的情況下,執行到對同一個對象加鎖的 synchronized 代碼塊時,為串行執行的。這里注意,并不是同一個同步代碼塊,而是對同一個對象上鎖的同步代碼塊。這意味著范圍更廣。此外 synchronized 可以確保可見性,在一個線程執行完 synchronized 代碼后,所有代碼中對變量值的變化都能立即被其它線程所看到。 由于 synchronized 關鍵字會使得代碼串行執行,這就喪失了多線程的優勢。并且 synchronized 關鍵字的使用也有相應成本。所以我們代碼中能不用 synchronized 就不用。當不得不用的時候,需要盡量控制 synchronized 代碼塊中的代碼行數。這就像高速公路上,本來三車道,所有車輛開得很快,但是突然遇到檢查點,車輛只能一輛一輛通過,那么速度一下就慢了下來,必然造成堵車。我們應該盡量減少這種人為堵點。 ![圖片描述](https://img.mukewang.com/5da54f3c00018b5c10570629.jpg) ## 2、synchronized 的使用 synchronized 的使用非常簡單,有兩種方式,第一種是同步代碼塊。 我們拿之前例子的代碼片段回顧下: ~~~java synchronized (tasks) { if (tasks.size() > 0) { task = tasks.removeFirst(); sleep(100); tasks.notifyAll(); } else { tasks.wait(); } } ~~~ ~~~java synchronized (tasks) { if (tasks.size() < MAX) { Task task = new Task(new Random().nextInt(3) + 1, getPunishedWord()); tasks.addLast(task); System.out.println(threadName + "留了作業,抄寫" + task.getWordToCopy() + " " + task.getLeftCopyCount() + "次"); tasks.notifyAll(); } else { System.out.println(threadName+"開始等待"); tasks.wait(); System.out.println("teacher線程 " + threadName + "線程-" + name + "等待結束"); } } ~~~ 這是生產者/消費者那一節的部分代碼。第一段是學生寫作業的代碼,第二段是老師留作業的代碼。可以看到 synchronized 的使用很簡單,把你需要同步的代碼放入 synchronized 關鍵字后面的大括號中即可。 另外你肯定注意到 synchronized (tasks) ,這行代碼小括號里的 tasks 對象。為什么要這么寫呢?這是和 synchronized 實現的方式相關的。你是不是心里在想:這個對象一定是被加鎖的對象,加了鎖之后,別的線程就不能對該對象訪問了。這里理解起來好像非常的自然。其實并不是這樣,小括號里的對象是可以是任意的對像。之前我們講解過這一點,這個對象相當于是同步代碼塊的看門人,每個對其 synchronized 的線程,它都會記錄下來,然后等到同步代碼塊沒有線程執行的時候,它就會通知其它線程來執行同步代碼塊。 所以我們并不是對此對象加鎖,只是讓它來維護秩序。這個人是誰其實并無所謂。但是我們的例子中,并發的線程并不是同樣類型的 Thread,一個是 Student,還一個是 Teacher。對于不同對象的同步控制,一定要選用兩個線程都持有的對象才行。否則各自使用不同的對象,相當于聘用了兩個看門人,各看各的門,毫無瓜葛。那么原本想要串行執行的代碼仍舊會并行執行。 第二種,使用 synchronized 關鍵字修飾方法: ~~~java public synchronized void eat(){ ....... ....... } ~~~ 你是不是會好奇,這里沒有鎖對象,是如何加鎖的呢?其實同步方法的鎖對象就是 this。這和下面代碼把方法中代碼全部用 synchronized(this) 括起來的效果是一樣的: ~~~java public void eat(){ synchronized(this){ ....... ....... } } ~~~ 如果是 synchroinized 的是靜態方法,如下面代碼: ~~~java public static synchronized void eat(){ ....... ....... } ~~~ 此時同步方法為類的 Class 對象。如果上述靜態方法所在的類為 Test。那么鎖對象就是 Test.class。 構造方法是不能使用 synchronized 關鍵字修飾的。因為同步的構造方法是講不通的,對于一個指定的對象,它只會有唯一的創建線程,所以不需要使用 synchroinzied 修飾。 下面是 synchronized 的使用總結: 1、選用一個鎖對象,可以是任意對象; 2、鎖對象鎖的是同步代碼塊,并不是自己; 3、不同類型的多個 Thread 如果有代碼要同步執行,鎖對象要使用所有線程共同持有的同一個對象; 4、需要同步的代碼放到大括號中。需要同步的意思就是需要保證原子性、可見性、有序性中的任何一種或多種。不要放不需要同步的代碼進來,影響代碼效率。 ## 3、synchronized 原理 synchronized 的秘密其實都在同步對象上。就像上文所說,這個對象就是一個看門人,每次只允許一個線程進來,進門后此線程可以做任何自己想做的事情,然后再出來。此時看門人會吼一嗓子:沒人了,可以進來啦!其它線程聽到吼聲,馬上都沖了過來。但總有個敏捷值最高的線程先沖入門內,那么其它線程只好繼續等待。 其實 synchronized 原理基本和上面的例子一樣。下面我們真正來看看其實現原理是什么。相信如果你看懂了上面的例子,對 synchronized 原理的理解不會有任何難度。 我們一直說的同步對象,其實就是任何一個普通的對象。那么一個普通的java對象是如何來做同步這件事的呢?這是因為每個對象都關聯了一個 monitor lock。 當一個線程獲取了 monitor lock 后,其它線程如果運行到獲取同一個 monitor 的時候就會被 block 住。當這個線程執行完同步代碼,則會釋放 monitor lock。在后一個線程獲取鎖后,happens-before 原則生效,前一個線程所做的任何修改都會被這個線程看到。 我們再深入底層一點來分析。每個 Java 對象在 JVM 的對等對象的頭中保存鎖狀態,指向 ObjectMonitor。ObjectMonitor 保存了當前持有鎖的線程引用,EntryList 中保存目前等待獲取鎖的線程,WaitSet 保存 wait 的線程。此外還有一個計數器,每當線程獲得 monitor 鎖,計數器 +1,當線程重入此鎖時,計數器還會 +1。當計數器不為0時,其它嘗試獲取 monitor 鎖的線程將會被保存到EntryList中,并被阻塞。當持有鎖的線程釋放了monitor 鎖后,計數器 -1。當計數器歸位為 0 時,所有 EntryList 中的線程會嘗試去獲取鎖,但只會有一個線程會成功,沒有成功的線程仍舊保存在 EntryList 中。**由此可以看出 monitor 鎖是非公平鎖**。 我們看一下前面例子中 Student 類編譯之后的匯編指令。或者你也可以自己寫一段簡單的帶有 synchronized 關鍵字的代碼。先將其編譯為.class 文件,然后使用 javap -c xxx.class 進行反匯編。我們就可以得到 java 代碼對應的匯編指令。里面可以找到如下兩行指令。 ~~~ ...... 15: monitorenter ...... 128: monitorexit ...... ~~~ 這兩條指令就是上面所講述的獲取鎖和釋放鎖的關鍵指令。我看過使用 zookeepe r實現分布式鎖的 Curator 框架源代碼,Curator 的互斥鎖和 monitor 鎖在原理上一模一樣。 ## 4、synchronized 使用注意 1. synchronized 使用的為非公平鎖,如果你需要公平鎖,那么不要使用 synchronized。可以使用 ReentrantLock,設置為公平鎖。關于 ReentrantLock,會在后面章節進行講解; 2. 鎖對象不能為 null。如果鎖對象為 null,何談對象頭,以及保存與其關聯的 monitor 鎖呢?所以代碼中要確保synchronized使用的鎖對象不為 null; 3. 只把需要同步的代碼放入 synchronized 代碼塊。如果不思考,為了線程安全把方法中全部代碼都放入同步代碼塊,那么將會喪失多線程的優勢。再多的線程也只能串行執行,這完全違背了并發的初衷; 4. 只有使用同一個對象作為鎖對象,才能同步。記住是同一個對象,而不是同一個類。有一種常犯的錯誤是,不同線程持有的是同一個類的不同實例。那么該對象實例用作鎖對象的話,多個線程并不會同步。還一種錯誤是使用不同類的實例作為鎖對象,但是期望不同位置的同步代碼塊能夠同步執行。這是不可能達到你想要的效果的。 ## 5、總結 本節我們學習了Java多線程領域使用最多的同步方式 synchronized 關鍵字。synchronized 使用方便簡單,但是一定注意其作用范圍不要過大。另外 synchronized 也有其局限性。我們在后面會學習到 Lock 接口及其實現,可以解決 synchronized 存在的問題。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看