1. 程式人生 > >Java中的關鍵字synchronized

Java中的關鍵字synchronized

1. 介紹

Java併發系列的文章中,這個是第二篇文章。在前面的一篇文章中,我們學習了Java中的Executor池和Excutors的各種類別。

在這篇文章中,我們會學習synchronized關鍵字以及我們在多執行緒的環境中如何使用。

2. 什麼是同步?

在一個多執行緒的環境中,多個執行緒同時訪問相同的資源的情況是存在的。例如,兩個執行緒試圖寫入同一個文字檔案。它們之間沒有任何的同步,當兩個或多個執行緒對同一檔案具有寫訪問權時,寫入該檔案的資料可能會損壞。
同理,在JVM中,每個執行緒在各自的棧上儲存了一份變數的副本。某些其他執行緒可能會更改這些變數的實際值。但是更改後的值可能不會重新整理到其他執行緒的本地副本中。

這可能導致程式執行錯誤和非確定性行為。

為了避免這種問題,Java給我們提供了synchronized這有助於實現執行緒之間的通訊,使得只有一個執行緒訪問同步資源,而其他執行緒等待資源變為空閒。

synchronized關鍵字可以被用在下面一些不同的方式中,比如一個同步塊:

synchronized(someobject){
    //thread-safe code here
}

對方法進行同步:

public synchronized void someMethod(){
    //thread-safe code here
}

3.在JVM中synchronized是如何實現的

當一個執行緒試圖進入一個同步塊或者同步方法中的時候,它必須先獲得一個同步物件上的鎖。一次只可以有一個執行緒獲取鎖,並且執行塊中的程式碼。

如果其他執行緒嘗試訪問該同步塊,則必須等待,直到當前執行緒執行完同步塊的程式碼。當前執行緒退出後,鎖將被自動釋放,其它執行緒可以獲取鎖並進入同步程式碼塊。

  • 對於一個synchronized塊來說,在synchronized關鍵字後的括號中指定的物件上獲取鎖;
  • 對於一個synchronized static方法,鎖是在.class物件上獲取的;
  • 對於synchronized例項方法來說,鎖定是在該類的當前例項上獲得的,即該例項(this);

4.同步方法

定義同步方法就像在返回型別之前簡單地包含關鍵字一樣簡單。我們定義一個順序列印數字1-5之間的方法。會有兩個執行緒來訪問這個方法,所以讓我們來看看在沒有使用synchronized關鍵字它們的執行情況, 和我們使用關鍵字來鎖住共享物件會發生什麼:

public class NonSynchronizedMethod {

    public void printNumbers() {
        System.out.println("Starting to print Numbers for " + Thread.currentThread().getName());

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }

        System.out.println("Completed printing Numbers for " + Thread.currentThread().getName());
    }
}

現在,讓我們實現兩個訪問該物件並希望執行 printNumbers() 方法的自定義執行緒:

class ThreadOne extends Thread {

    NonSynchronizedMethod nonSynchronizedMethod;

    public ThreadOne(NonSynchronizedMethod nonSynchronizedMethod) {
        this.nonSynchronizedMethod = nonSynchronizedMethod;
    }

    @Override
    public void run() {
        nonSynchronizedMethod.printNumbers();
    }
}

class ThreadTwo extends Thread {

    NonSynchronizedMethod nonSynchronizedMethod;

    public ThreadTwo(NonSynchronizedMethod nonSynchronizedMethod) {
        this.nonSynchronizedMethod = nonSynchronizedMethod;
    }

    @Override
    public void run() {
        nonSynchronizedMethod.printNumbers();
    }
}

這些執行緒共享一個相同的物件NonSynchronizedMethod,它們會在這個物件上同時去呼叫非同步的方法printNumbers()

為了測試這個,寫一個main方法來做測試:

public class TestSynchronization {  
    public static void main(String[] args) {

        NonSynchronizedMethod nonSynchronizedMethod = new NonSynchronizedMethod();

        ThreadOne threadOne = new ThreadOne(nonSynchronizedMethod);
        threadOne.setName("ThreadOne");

        ThreadTwo threadTwo = new ThreadTwo(nonSynchronizedMethod);
        threadTwo.setName("ThreadTwo");

        threadOne.start();
        threadTwo.start();

    }
}

執行上面的程式碼,我們會得到下面的結果:

Starting to print Numbers for ThreadOne  
Starting to print Numbers for ThreadTwo  
ThreadTwo 0  
ThreadTwo 1  
ThreadTwo 2  
ThreadTwo 3  
ThreadTwo 4  
Completed printing Numbers for ThreadTwo  
ThreadOne 0  
ThreadOne 1  
ThreadOne 2  
ThreadOne 3  
ThreadOne 4  
Completed printing Numbers for ThreadOne

雖然ThreadOne先開始執行的,但是ThreadTwo先結束的。

當我們再次執行上面的程式的時候,我們會得到一個不同的結果:

Starting to print Numbers for ThreadOne  
Starting to print Numbers for ThreadTwo  
ThreadOne 0  
ThreadTwo 0  
ThreadOne 1  
ThreadTwo 1  
ThreadOne 2  
ThreadTwo 2  
ThreadOne 3  
ThreadOne 4  
ThreadTwo 3  
Completed printing Numbers for ThreadOne  
ThreadTwo 4  
Completed printing Numbers for ThreadTwo

這些輸出完全是偶然的,完全不可預測。每次執行都會給我們一個不同的輸出。因為可以有更多的執行緒,我們可能會遇到問題。在實際場景中,在訪問某種型別的共享資源(如檔案或其他型別的IO)時,這一點尤為重要,而不是僅僅列印到控制檯。

下面我們採用同步的方法,使用synchronized關鍵字:

public synchronized void printNumbers() {  
    System.out.println("Starting to print Numbers for " + Thread.currentThread().getName());

    for (int i = 0; i < 5; i++) {
        System.out.println(Thread.currentThread().getName() + " " + i);
    }

    System.out.println("Completed printing Numbers for " + Thread.currentThread().getName());
}

程式碼中只是給方法添加了一個synchronized關鍵字,沒有其它的改動。現在我們執行上面的程式碼,得到如下所示的結果:

Starting to print Numbers for ThreadOne  
ThreadOne 0  
ThreadOne 1  
ThreadOne 2  
ThreadOne 3  
ThreadOne 4  
Completed printing Numbers for ThreadOne  
Starting to print Numbers for ThreadTwo  
ThreadTwo 0  
ThreadTwo 1  
ThreadTwo 2  
ThreadTwo 3  
ThreadTwo 4  
Completed printing Numbers for ThreadTwo

在這裡,我們看到即使兩個執行緒同時執行,只有一個執行緒一次進入synchronized方法,在這種情況下是ThreadOne。一旦完成執行,ThreadTwo就可以執行printNumbers()方法

5.同步塊

多執行緒的主要目的是儘可能並行地執行任意數量的任務。但是,同步限制了必須執行同步方法或塊的執行緒的並行性。

但是,我們可以嘗試通過在同步範圍內保留儘可能少的程式碼來減少以同步方式執行的程式碼量。可能有許多場景,不是在整個方法上同步,而是可以在方法中同步幾行程式碼。

我們可以使用synchronized塊來包含程式碼的那部分而不是整個方法。也就是說對於需要同步的程式碼塊進行同步,而不是對整個方法進行同步。

由於在同步塊內部執行的程式碼量較少,因此每個執行緒都會更快地釋放鎖定。結果,其他執行緒花費更少的時間等待鎖定並且程式碼吞吐量大大增加。

讓我們修改前面的例子,只同步for迴圈列印數字序列,實際上,它是我們示例中應該同步的唯一程式碼部分:

public class SynchronizedBlockExample {
    public void printNumbers() {
        System.out.println("Starting to print Numbers for " + Thread.currentThread().getName());
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
        System.out.println("Completed printing Numbers for " + Thread.currentThread().getName());
    }
}

執行結果:

Starting to print Numbers for ThreadOne  
Starting to print Numbers for ThreadTwo  
ThreadOne 0  
ThreadOne 1  
ThreadOne 2  
ThreadOne 3  
ThreadOne 4  
Completed printing Numbers for ThreadOne  
ThreadTwo 0  
ThreadTwo 1  
ThreadTwo 2  
ThreadTwo 3  
ThreadTwo 4  
Completed printing Numbers for ThreadTwo

儘管ThreadTwoThreadOne完成其任務之前“開始”列印數字似乎令人擔憂,這只是因為我們在停止ThreadTwo鎖之前,允許執行緒通過System.out.println("Completed printing Numbers for " + Thread.currentThread().getName())語句。

這很好,因為我們只想同步每個執行緒中的數字序列。我們可以清楚地看到兩個執行緒只是通過同步for迴圈以正確的順序列印數字。

6.結論

在這個例子中,我們看到了如何在Java中使用synchronized關鍵字來實現多個執行緒之間的同步。我們還通過例子瞭解了何時可以使用synchronized方法和塊。

與往常一樣,您可以找到此示例中使用的程式碼

原文:https://stackabuse.com/synchronized-keyword-in-java/

 

作者:Chandan Singh

 

譯者:lee

相關推薦

巨人大哥談JavaSynchronized關鍵字用法

技術 class method state 總結 object oid ack body 巨人大哥談Java中的Synchronized關鍵字用法 認識synchronized 對於寫多線程程序的人來說,經常碰到的就是並發問題,對於容易出現並發問題的地方價格synchron

JavaSynchronized關鍵字用法

認識synchronized 對於寫多執行緒程式的人來說,經常碰到的就是併發問題,對於容易出現併發問題的地方加上synchronized修飾符基本上就搞定 了,如果說不考慮效能問題的話,這一招絕對能應對百分之九十以上的情況,若對於效能方面有要求的話就需要額外的知識比如讀寫鎖等等。本文目的先了解透徹synch

Javasynchronized、volatile、ReenTrantLock、AtomicXXX

包含 指令 純粹 功能性 title 伸縮 其它 同步問題 留下 多線程和並發性並不是什麽新內容,但是 Java 語言設計中的創新之一就是,它是第一個直接把跨平臺線程模型和正規的內存模型集成到語言中的主流語言。核心類庫包含一個 Thread 類,可以用它來構建、啟動和操縱線

java關鍵字

擴展 聲明 這樣的 his tile IT 實現 cat 構造 訪問控制 private protected public 省略 類,方法和變量修飾符 abstract class extends final implements

java關鍵字和名詞理解

模式 大致 bsp 抽象類 抽象方法 定義 實現 基礎上 實例 1、抽象類: 抽象類體現的是一種模板模式的設計,抽象類作為多個子類的通用模板,其中部分方法已經實現,也提供部分抽象方法,推遲到子類中去實現。所以子類在抽象類的基礎上進行擴展,改造,但子類總體上會大致保留抽象類的

自頂向下徹底理解 Java Synchronized

閱讀本文至少要知道 synchronized 用來是幹什麼的... 需要的前置知識還有 Java 物件頭和 Java 位元組碼的部分知識。 synchronized 的使用 synchronized 有三種使用方式,三種方式鎖住的物件是不相同的。 鎖分為例項物件鎖和 class 物件鎖 和 類物件鎖

深入理解Javasynchronized鎖重入

問題匯入:如果一個執行緒呼叫了一個物件的同步方法,那麼他還能不能在呼叫這個物件的另外一個同步方法呢? 這裡就是synchronized鎖重入問題。 一.synchronized鎖重入  來看下面的程式碼: .這個是三個同步方法的類 public class Syn

Java同步關鍵字synchronized詳解

前言 多執行緒程式設計可以極大地提高了效率,但也會帶來執行緒安全問題。比如說多個執行緒向資料庫插入資料,就可能會導致資料庫中資料重複。 什麼時候會引發執行緒安全問題 首先我需要了解什麼是臨界資源?有這樣一種資源,在某一時刻只能被一個執行緒所使用,這種資源可以是各

Java關鍵字throw和throws的區別

丟擲異常有三種形式 throw throws 系統自動拋異常 一、系統自動拋異常 當程式語句出現一些邏輯錯誤、主義錯誤或型別轉換錯誤時,系統會自動丟擲異常:(舉個栗子) public static void main(String[] args) {

Java關鍵字小總結!

1、訪問控制符 public:公共的,其限制最小 protected:受保護的,通常作為繼承許可權 default:預設的。 private:私有的,其限制最大。 修飾類的訪問控制符:public和預設的訪問控制符;修飾普通方法(構造方法)或者屬性的訪問控制符:上述

java關鍵字 this 和super的作用及用法

this關鍵字1)在類的內部代表物件本身,你應該看到過this.xxx(),this.xxx這種用法吧,this就代表這個類的物件,比如public class A { private String name; public void setName(String name)

java同步synchronized的意義,如何用它解決執行緒不安全的問題

馬克-to-win:從上節我們學到,當多個執行緒訪問且更改同一個變數時,很容易出現執行緒安全問題,誠然,我們可以通過一些其他手段,比如區域性變數,多個例項,調整程式結構來解決執行緒安全問題,但是通常來講,通過同步機制s

java關鍵字的定義以及如何識別關鍵字

/*  * 關鍵字:被java語言賦予特定含義的單詞  *   * 在java中:關鍵字有一種特殊的顏色標記:工具中:eclipse/myEclipse中:大紅色  *   * 注意事項:  * goto/const:作為保留字存在,目前不使用!  *   *   * :判

javasynchronized釋放鎖的時機

任何執行緒進入同步程式碼塊、同步方法之前,必須獲得同步監視器的鎖定,那麼何時會釋放這個鎖定呢?在程式中,是無法顯式釋放對同步監視器的鎖的,而會在如下幾個情況下釋放鎖。 1、當前執行緒的同步方法、程式碼塊執行結束的時候釋放 2、當前執行緒在同步方法、同步程式碼塊中遇到b

Java同步關鍵字Synchronized深入理解

題記 講講寫這篇部落格的原因,因為自認為對synchronized這個關鍵字很瞭解了,前幾天和一個剛好在找工作的朋友聊到了這個。結果他把面試遇到的一個問題給我出了出來,當我蒙圈的那一刻才懂得自己之前的瞭解只是皮毛。 正文 對於synchronized這

javasynchronized同步程式碼塊和同步方法的區別

問題的由來: 看到這樣一個面試題: //下列兩個方法有什麼區別 public synchronized void method1(){} public void method2(){ synchronized (obj){} } synchronized用

java同步(synchronized)訪問共享的可變資料及原子性操作

當多個執行緒共享可變資料的時候,每個讀或者寫資料的執行緒都必須執行同步。如果沒有同步,就無法保證一個執行緒所做的修改可以被另外一個執行緒獲知。未能同步共享可變資料會造成程式的活性失敗(liveness failure)和安全性失敗(safety failure)

Javasynchronized關鍵字理解

監視器 pre 定義 exc 執行 zed 三種 gen 好記性不如爛筆頭 好記性不如爛筆頭~~ 並發編程中synchronized關鍵字的地位很重要,很多人都稱它為重量級鎖。利用synchronized實現同步的基礎:Java中每一個對象都可以作為鎖。具體表現為以下三種形

Javasynchronized關鍵字使用實踐

1、synchronized修飾類的普通方法 package main.thread; /** * Created by leboop on 2018/11/18. * 測試synchronized關鍵字使用 */ public class SynchronizedClass {

javasynchronized關鍵字的認識&記錄

背景 在工作中經常會遇到需要做執行緒同步處理的場景,但由於一直對執行緒同步是一知半解,沒有很系統的去了解過,於是最近終於踩到了由於synchronized同步應用不當導致app頻繁ANR無響應的坑了,下面就這次踩坑來對synchronized執行緒同步來做一個