Java 多線程乾貨系列—(二)synchronized | 掘金技術徵文

本來地址:Java多線程乾貨系列—(二)synchronized
博客地址:tengj.top/java

前言

本篇主要介紹Java多線程中的同步,也就是如何在Java語言中寫出線程安全的程序,如何在Java語言中解決非線程安全的相關問題。沒錯就是使用synchronized。程序員

正文

如何解決線程安全問題?

那麼通常來講,是如何解決線程安全問題的呢?面試

基本上全部的併發模式在解決線程安全問題時,都採用「序列化訪問臨界資源」的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱做同步互斥訪問。安全

一般來講,是在訪問臨界資源的代碼前面加上一個鎖,當訪問完臨界資源後釋放鎖,讓其餘線程繼續訪問。微信

在Java中,提供了兩種方式來實現同步互斥訪問:synchronized和Lock。多線程

本文主要講述synchronized的使用方法,Lock的使用方法在下一篇博文中講述。併發

synchronized同步方法

synchronized是Java語言的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,可以保證在同一時刻最多隻有一個線程執行該段代碼。在瞭解synchronized關鍵字的使用方法以前,咱們先來看一個概念:互斥鎖,顧名思義:能到達到互斥訪問目的的鎖。ide

舉個簡單的例子:若是對臨界資源加上互斥鎖,當一個線程在訪問該臨界資源時,其餘線程便只能等待。oop

在Java中,每個對象都擁有一個鎖標記(monitor),也稱爲監視器,多線程同時訪問某個對象時,線程只有獲取了該對象的鎖才能訪問。post

在Java中,可使用synchronized關鍵字來標記一個方法或者代碼塊,當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便得到了該對象的鎖,其餘線程暫時沒法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程纔會釋放該對象的鎖,其餘線程才能執行這個方法或者代碼塊。

synchronized的使用

  • synchronized代碼塊,被修飾的代碼成爲同步語句塊,其做用的範圍是調用這個代碼塊的對象,咱們在用synchronized關鍵字的時候,能縮小代碼段的範圍就儘可能縮小,能在代碼段上加同步就不要再整個方法上加同步。這叫減少鎖的粒度,使代碼更大程度的併發。

  • synchronized方法,被修飾的方法成爲同步方法,其做用範圍是整個方法,做用對象是調用這個方法的對象。

  • synchronized靜態方法,修飾一個static靜態方法,其做用範圍是整個靜態方法,做用對象是這個類的全部對象。

  • synchronized類,其做用範圍是Synchronized後面括號括起來的部分synchronized(className.class),做用的對象是這個類的全部對象。

  • synchronized(),()中是鎖住的對象, synchronized(this)鎖住的只是對象自己,同一個類的不一樣對象調用的synchronized方法並不會被鎖住,而synchronized(className.class)實現了全局鎖的功能,全部這個類的對象調用這個方法都受到鎖的影響,此外()中還能夠添加一個具體的對象,實現給具體對象加鎖。

    synchronized (object) {
    //在同步代碼塊中對對象進行操做
    }複製代碼

synchronized注意事項

  • 當兩個併發線程訪問同一個對象中的synchronized代碼塊時,在同一時刻只能有一個線程獲得執行,另外一個線程受阻塞,必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。兩個線程間是互斥的,由於在執行synchronized代碼塊時會鎖定當前的對象,只有執行完該代碼塊才能釋放該對象鎖,下一個線程才能執行並鎖定該對象。

  • 當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。(兩個線程使用的是同一個對象)

  • 當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞(同上,兩個線程使用的是同一個對象)。

下面經過代碼來實現:

1)當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。

package ths;

public class Thread1 implements Runnable {  
     public void run() {  
          synchronized(this) {  
               for (int i = 0; i < 5; i++) {  
                    System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);  
               }  
          }  
     }  
     public static void main(String[] args) {  
          Thread1 t1 = new Thread1();  
          Thread ta = new Thread(t1, "A");  
          Thread tb = new Thread(t1, "B");  
          ta.start();  
          tb.start();  
     } 
}複製代碼

輸出結果:

A synchronized loop 0  
A synchronized loop 1  
A synchronized loop 2  
A synchronized loop 3  
A synchronized loop 4  
B synchronized loop 0  
B synchronized loop 1  
B synchronized loop 2  
B synchronized loop 3  
B synchronized loop 4複製代碼

2)然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。

package ths;

public class Thread2 {  
     public void m4t1() {  
          synchronized(this) {  
               int i = 5;  
               while( i-- > 0) {  
                    System.out.println(Thread.currentThread().getName() + " : " + i);  
                    try {  
                         Thread.sleep(500);  
                    } catch (InterruptedException ie) {  
                    }  
               }  
          }  
     }  
     public void m4t2() {  
          int i = 5;  
          while( i-- > 0) {  
               System.out.println(Thread.currentThread().getName() + " : " + i);  
               try {  
                    Thread.sleep(500);  
               } catch (InterruptedException ie) {  
               }  
          }  
     }  
     public static void main(String[] args) {  
          final Thread2 myt2 = new Thread2();  
          Thread t1 = new Thread(  new Runnable() {  public void run() {  myt2.m4t1();  }  }, "t1"  );  
          Thread t2 = new Thread(  new Runnable() {  public void run() { myt2.m4t2();   }  }, "t2"  );  
          t1.start();  
          t2.start();  
     } 
}複製代碼

輸出結果:

t1 : 4  
t2 : 4  
t1 : 3  
t2 : 3  
t1 : 2  
t2 : 2  
t1 : 1  
t2 : 1  
t1 : 0  
t2 : 0複製代碼

3)尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。

//修改Thread2.m4t2()方法: 
     public void m4t2() {  
          synchronized(this) {  
               int i = 5;  
               while( i-- > 0) {  
                    System.out.println(Thread.currentThread().getName() + " : " + i);  
                    try {  
                         Thread.sleep(500);  
                    } catch (InterruptedException ie) {  
                    }  
               }  
          }

     }複製代碼

輸出結果:

t1 : 4  
t1 : 3  
t1 : 2  
t1 : 1  
t1 : 0  
t2 : 4  
t2 : 3  
t2 : 2  
t2 : 1  
t2 : 0複製代碼

4)第三個例子一樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。

//修改Thread2.m4t2()方法以下:

     public synchronized void m4t2() {  
          int i = 5;  
          while( i-- > 0) {  
               System.out.println(Thread.currentThread().getName() + " : " + i);  
               try {  
                    Thread.sleep(500);  
               } catch (InterruptedException ie) {  
               }  
          }  
     }複製代碼

輸出結果:

t1 : 4  
t1 : 3  
t1 : 2  
t1 : 1  
t1 : 0  
t2 : 4  
t2 : 3  
t2 : 2  
t2 : 1  
t2 : 0複製代碼

5)每一個類也會有一個鎖,它能夠用來控制對static數據成員的併發訪問。
而且若是一個線程執行一個對象的非static synchronized方法,另一個線程須要執行這個對象所屬類的static synchronized方法,此時不會發生互斥現象,由於訪問static synchronized方法佔用的是類鎖,而訪問非static synchronized方法佔用的是對象鎖,因此不存在互斥現象。
代碼以下:

public class Test {

    public static void main(String[] args) {
        final InsertData insertData = new InsertData();
        new Thread(){
            @Override
            public void run() {
                insertData.insert();
            }
        }.start(); 
        new Thread(){
            @Override
            public void run() {
                insertData.insert1();
            }
        }.start();
    }  
}

class InsertData { 
    public synchronized void insert(){
        System.out.println("執行insert");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("執行insert完畢");
    }

    public synchronized static void insert1() {
        System.out.println("執行insert1");
        System.out.println("執行insert1完畢");
    }
}複製代碼

輸出結果:

執行insert
執行insert1
執行insert1完畢
執行insert完畢複製代碼

第一個線程裏面執行的是insert方法,不會致使第二個線程執行insert1方法發生阻塞現象。

面試題

當一個線程進入一個對象的synchronized方法A以後,其它線程是否可進入此對象的synchronized方法B?
答:不能。其它線程只能訪問該對象的非同步方法,同步方法則不能進入。由於非靜態方法上的synchronized修飾符要求執行方法時要得到對象的鎖,若是已經進入A方法說明對象鎖已經被取走,那麼試圖進入B方法的線程就只能在等鎖池(注意不是等待池哦)中等待對象的鎖。

synchronized關鍵字的用法?
答:synchronized關鍵字能夠將對象或者方法標記爲同步,以實現對對象和方法的互斥訪問,能夠用synchronized(對象) { … }定義同步代碼塊,或者在聲明方法時將synchronized做爲方法的修飾符。

簡述synchronized 和java.util.concurrent.locks.Lock的異同?
答:Lock是Java 5之後引入的新的API,和關鍵字synchronized相比主要相同點:Lock 能完成synchronized所實現的全部功能;主要不一樣點:Lock有比synchronized更精確的線程語義和更好的性能,並且不強制性的要求必定要得到鎖。synchronized會自動釋放鎖,而Lock必定要求程序員手工釋放,而且最好在finally 塊中釋放(這是釋放外部資源的最好的地方)

總結

以上就是synchronized的概念和基本使用用法,下一篇博文中將介紹Lock,但願對你有所幫助。


一直以爲本身寫的不是技術,而是情懷,一篇篇文章是本身這一路走來的痕跡。靠專業技能的成功是最具可複製性的,但願個人這條路能讓你少走彎路,但願我能幫你抹去知識的蒙塵,但願我能幫你理清知識的脈絡,但願將來技術之巔上有你也有我。

訂閱博主微信公衆號:嘟爺java超神學堂(javaLearn)三大好處:

  • 獲取最新博主博客更新信息,首發公衆號
  • 獲取大量視頻,電子書,精品破解軟件資源
  • 能夠跟博主聊天,歡迎程序媛妹妹來撩我

掘金技術徵文第三期:聊聊你的最佳實踐

相關文章
相關標籤/搜索