Java多線程之Synchronized的用法

synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:

  1. 修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;
  2. 修飾一個方法,被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象;
  3. 修改一個靜態的方法,其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象;
  4. 修改一個類,其作用的范圍是synchronized后面括號括起來的部分,作用主的對象是這個類的所有對象。

修飾一個代碼塊

一個線程訪問一個對象中的synchronized(this)同步代碼塊時,其他試圖訪問該對象的線程將被阻塞。我們看下面一個例子:
【Demo1】:synchronized的用法

/**

  • 同步線程
    */
    class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
    count = 0;
    }

    public void run() {
    synchronized(this) {
    for (int i = 0; i < 5; i++) {
    try {
    System.out.println(Thread.currentThread().getName() + ":" + (count++));
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

    public int getCount() {
    return count;
    }
    }

SyncThread的調用:

SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();

結果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9*
當兩個并發線程(thread1和thread2)訪問同一個對象(syncThread)中的synchronized代碼塊時,在同一時刻只能有一個線程得到執行,另一個線程受阻塞,必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊

。Thread1和thread2是互斥的,因為在執行synchronized代碼塊時會鎖定當前的對象,只有執行完該代碼塊才能釋放該對象鎖,下一個線程才能執行并鎖定該對象。
我們再把SyncThread的調用稍微改一下:

Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();

結果如下:

SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9
不是說一個線程執行synchronized代碼塊時其它的線程受阻塞嗎?為什么上面的例子中thread1和thread2同時在執行。這是因為synchronized只鎖定對象,每個對象只有一個鎖(lock)與之相關聯,而上面的代碼等

同于下面這段代碼:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

這時創建了兩個SyncThread的對象syncThread1和syncThread2,線程thread1執行的是syncThread1對象中的synchronized代碼(run),而線程thread2執行的是syncThread2對象中的synchronized代碼(run);我們知道

synchronized鎖定的是對象,這時會有兩把鎖分別鎖定syncThread1對象和syncThread2對象,而這兩把鎖是互不干擾的,不形成互斥,所以兩個線程可以同時執行。

2.當一個線程訪問對象的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該對象中的非synchronized(this)同步代碼塊。
【Demo2】:多個線程訪問synchronized和非synchronized代碼塊

class Counter implements Runnable{
private int count;

public Counter() {
count = 0;
}

public void countAdd() {
synchronized(this) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//非synchronized代碼塊,未對count進行讀寫操作,所以可以不用synchronized
public void printCount() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + " count:" + count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.equals("A")) {
countAdd();
} else if (threadName.equals("B")) {
printCount();
}
}
}

調用代碼:

Counter counter = new Counter();
Thread thread1 = new Thread(counter, "A");
Thread thread2 = new Thread(counter, "B");
thread1.start();
thread2.start();

結果如下:

A:0
B count:1
A:1
B count:2
A:2
B count:3
A:3
B count:4
A:4
B count:5
上面代碼中countAdd是一個synchronized的,printCount是非synchronized的。從上面的結果中可以看出一個線程訪問一個對象的synchronized代碼塊時,別的線程可以訪問該對象的非synchronized代碼塊而不受阻

塞。

指定要給某個對象加鎖
【Demo3】:指定要給某個對象加鎖

/**

  • 銀行賬戶類
    */
    class Account {
    String name;
    float amount;

    public Account(String name, float amount) {
    this.name = name;
    this.amount = amount;
    }
    //存錢
    public void deposit(float amt) {
    amount += amt;
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    //取錢
    public void withdraw(float amt) {
    amount -= amt;
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    public float getBalance() {
    return amount;
    }
    }

/**

  • 賬戶操作類
    */
    class AccountOperator implements Runnable{
    private Account account;
    public AccountOperator(Account account) {
    this.account = account;
    }

    public void run() {
    synchronized (account) {
    account.deposit(500);
    account.withdraw(500);
    System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
    }
    }
    }

調用代碼:

Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);

final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}

結果如下:

Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0
在AccountOperator 類中的run方法里,我們用synchronized 給account對象加了鎖。這時,當一個線程訪問account對象時,其他試圖訪問account對象的線程將會阻塞,直到該線程訪問account對象結束。也就是說

誰拿到那個鎖誰就可以運行它所控制的那段代碼。
當有一個明確的對象作為鎖時,就可以用類似下面這樣的方式寫程序。

public void method3(SomeObject obj)
{
//obj 鎖定的對象
synchronized(obj)
{
// todo
}
}

當沒有明確的對象作為鎖,只是想讓一段代碼同步時,可以創建一個特殊的對象來充當鎖:

class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance變量
public void method()
{
synchronized(lock) {
// todo 同步代碼塊
}
}

public void run() {

}
}

說明:零長度的byte數組對象創建起來將比任何對象都經濟――查看編譯后的字節碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。

修飾一個方法

Synchronized修飾一個方法很簡單,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修飾方法和修飾一個代碼塊類似,只是作用范圍不一樣,修飾代碼塊是大括號括

起來的范圍,而修飾方法范圍是整個函數。如將【Demo1】中的run方法改成如下的方式,實現的效果一樣。

*【Demo4】:synchronized修飾一個方法

public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Synchronized作用于整個方法的寫法。
寫法一:

public synchronized void method()
{
// todo
}

寫法二:

public void method()
{
synchronized(this) {
// todo
}
}

寫法一修飾的是一個方法,寫法二修飾的是一個代碼塊,但寫法一與寫法二是等價的,都是鎖定了整個方法時的內容。

在用synchronized修飾方法時要注意以下幾點:

  1. synchronized關鍵字不能繼承。
    雖然可以使用synchronized來定義方法,但synchronized并不屬于方法定義的一部分,因此,synchronized關鍵字不能被繼承。如果在父類中的某個方法使用了synchronized關鍵字,而在子類中覆蓋了這個方法,在

子類中的這個方法默認情況下并不是同步的,而必須顯式地在子類的這個方法中加上synchronized關鍵字才可以。當然,還可以在子類方法中調用父類中相應的方法,這樣雖然子類中的方法不是同步的,但子類調用

了父類的同步方法,因此,子類的方法也就相當于同步了。這兩種方式的例子代碼如下:
在子類方法中加上synchronized關鍵字

class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public synchronized void method() { }
}

在子類方法中調用父類的同步方法

class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public void method() { super.method(); }
}

在定義接口方法時不能使用synchronized關鍵字。
構造方法不能使用synchronized關鍵字,但可以使用synchronized代碼塊來進行同步。
修飾一個靜態的方法

Synchronized也可修飾一個靜態方法,用法如下:

public synchronized static void method() {
// todo
}

我們知道靜態方法是屬于類的而不屬于對象的。同樣的,synchronized修飾的靜態方法鎖定的是這個類的所有對象。我們對Demo1進行一些修改如下:

【Demo5】:synchronized修飾靜態方法

/**

  • 同步線程
    */
    class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
    count = 0;
    }

    public synchronized static void method() {
    for (int i = 0; i < 5; i ++) {
    try {
    System.out.println(Thread.currentThread().getName() + ":" + (count++));
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }

    public synchronized void run() {
    method();
    }
    }

調用代碼:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

結果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
syncThread1和syncThread2是SyncThread的兩個對象,但在thread1和thread2并發執行時卻保持了線程同步。這是因為run中調用了靜態方法method,而靜態方法是屬于類的,所以syncThread1和syncThread2相當于用

了同一把鎖。這與Demo1是不同的。

修飾一個類

Synchronized還可作用于一個類,用法如下:

class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}

我們把Demo5再作一些修改。
【Demo6】:修飾一個類

/**

  • 同步線程
    */
    class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
    count = 0;
    }

    public static void method() {
    synchronized(SyncThread.class) {
    for (int i = 0; i < 5; i ++) {
    try {
    System.out.println(Thread.currentThread().getName() + ":" + (count++));
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

    public synchronized void run() {
    method();
    }
    }

其效果和【Demo5】是一樣的,synchronized作用于一個類T時,是給這個類T加鎖,T的所有對象用的是同一把鎖。

總結:

A. 無論synchronized關鍵字加在方法上還是對象上,如果它作用的對象是非靜態的,則它取得的鎖是對象;如果synchronized作用的對象是一個靜態方法或一個類,則它取得的鎖是對類,該類所有的對象同一把鎖。
B. 每個對象只有一個鎖(lock)與之相關聯,誰拿到這個鎖誰就可以運行它所控制的那段代碼。
C. 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,476評論 1 15
  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 2,975評論 1 18
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,717評論 18 399
  • synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種: 1. 修飾一個代碼塊,被修飾...
    獅心王Richards閱讀 291評論 0 0
  • 自從買了電子閱讀器,看紙質書的機會就變少了。幾個月前,天降長假,漫長而悠閑的時間來得讓人措手不及。我選擇延續一貫的...
    樂癮閱讀 2,242評論 0 0