多線程「基礎篇」04之 synchronized關鍵字

本章,會對synchronized關鍵字進行介紹。涉及到的內容包括:
1. synchronized原理
2. synchronized基本規則
3. synchronized方法 和 synchronized代碼塊
4. 實例鎖 和 全局鎖 html

1. synchronized原理

在java中,每個對象有且僅有一個同步鎖。這也意味着,同步鎖是依賴於對象而存在。
當咱們調用某對象的synchronized方法時,就獲取了該對象的同步鎖。例如,synchronized(obj)就獲取了「obj這個對象」的同步鎖。
不一樣線程對同步鎖的訪問是互斥的。也就是說,某時間點,對象的同步鎖只能被一個線程獲取到!經過同步鎖,咱們就能在多線程中,實現對「對象/方法」的互斥訪問。 例如,如今有兩個線程A和線程B,它們都會訪問「對象obj的同步鎖」。假設,在某一時刻,線程A獲取到「obj的同步鎖」並在執行一些操做;而此時,線程B也企圖獲取「obj的同步鎖」 —— 線程B會獲取失敗,它必須等待,直到線程A釋放了「該對象的同步鎖」以後線程B才能獲取到「obj的同步鎖」從而才能夠運行。java

 

2. synchronized基本規則

咱們將synchronized的基本規則總結爲下面3條,並經過實例對它們進行說明。
第一條: 當一個線程訪問「某對象」的「synchronized方法」或者「synchronized代碼塊」時,其餘線程對「該對象」的該「synchronized方法」或者「synchronized代碼塊」的訪問將被阻塞。
第二條: 當一個線程訪問「某對象」的「synchronized方法」或者「synchronized代碼塊」時,其餘線程仍然能夠訪問「該對象」的非同步代碼塊
第三條: 當一個線程訪問「某對象」的「synchronized方法」或者「synchronized代碼塊」時,其餘線程對「該對象」的其餘的「synchronized方法」或者「synchronized代碼塊」的訪問將被阻塞。多線程

 

第一條

當一個線程訪問「某對象」的「synchronized方法」或者「synchronized代碼塊」時,其餘線程對「該對象」的該「synchronized方法」或者「synchronized代碼塊」的訪問將被阻塞。
下面是「synchronized代碼塊」對應的演示程序。ide

class MyRunable implements Runnable { 
     
    @Override 
    public void run() { 
        synchronized(this) { 
            try {  
                for (int i = 0; i < 5; i++) { 
                    Thread.sleep(100); // 休眠100ms 
                    System.out.println(Thread.currentThread().getName() + " loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }
}

public class Demo1_1 {

    public static void main(String[] args) {  
        Runnable demo = new MyRunable();     // 新建「Runnable對象」

        Thread t1 = new Thread(demo, "t1");  // 新建「線程t1」, t1是基於demo這個Runnable對象
        Thread t2 = new Thread(demo, "t2");  // 新建「線程t2」, t2是基於demo這個Runnable對象
        t1.start();                          // 啓動「線程t1」
        t2.start();                          // 啓動「線程t2」 
    } 
}

運行結果oop

複製代碼

t1 loop 0t1 loop 1t1 loop 2t1 loop 3t1 loop 4t2 loop 0t2 loop 1t2 loop 2t2 loop 3t2 loop 4

複製代碼

結果說明
run()方法中存在「synchronized(this)代碼塊」,並且t1和t2都是基於"demo這個Runnable對象"建立的線程。這就意味着,咱們能夠將synchronized(this)中的this看做是「demo這個Runnable對象」;所以,線程t1和t2共享「demo對象的同步鎖」。因此,當一個線程運行的時候,另一個線程必須等待「運行線程」釋放「demo的同步鎖」以後才能運行。this

若是你確認,你搞清楚這個問題了。那咱們將上面的代碼進行修改,而後再運行看看結果怎麼樣,看看你是否會迷糊。修改後的源碼以下:spa

class MyThread extends Thread { 
     
    public MyThread(String name) { 
        super(name); 
    } 
 
    @Override 
    public void run() { 
        synchronized(this) {
             try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 休眠100ms
                    System.out.println(Thread.currentThread().getName() + " loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }
}

public class Demo1_2 {

    public static void main(String[] args) {  
        Thread t1 = new MyThread("t1");  // 新建「線程t1」
        Thread t2 = new MyThread("t2");  // 新建「線程t2」
        t1.start();                          // 啓動「線程t1」
        t2.start();                          // 啓動「線程t2」 
    } 
}

代碼說明
比較Demo1_2 和 Demo1_1,咱們發現,Demo1_2中的MyThread類是直接繼承於Thread,並且t1和t2都是MyThread子線程。
幸運的是,在「Demo1_2的run()方法」也調用了synchronized(this),正如「Demo1_1的run()方法」也調用了synchronized(this)同樣!
那麼,Demo1_2的執行流程是否是和Demo1_1同樣呢?
運行結果:線程

複製代碼

t1 loop 0t2 loop 0t1 loop 1t2 loop 1t1 loop 2t2 loop 2t1 loop 3t2 loop 3t1 loop 4t2 loop 4

複製代碼

結果說明
若是這個結果一點也不令你感到驚訝,那麼我相信你對synchronized和this的認識已經比較深入了。不然的話,請繼續閱讀這裏的分析。
synchronized(this)中的this是指「當前的類對象」,即synchronized(this)所在的類對應的當前對象。它的做用是獲取「當前對象的同步鎖」。
對於Demo1_2中,synchronized(this)中的this表明的是MyThread對象,而t1和t2是兩個不一樣的MyThread對象,所以t1和t2在執行synchronized(this)時,獲取的是不一樣對象的同步鎖。對於Demo1_1對而言,synchronized(this)中的this表明的是MyRunable對象;t1和t2共同一個MyRunable對象,所以,一個線程獲取了對象的同步鎖,會形成另一個線程等待。code

 

第二條

當一個線程訪問「某對象」的「synchronized方法」或者「synchronized代碼塊」時,其餘線程仍然能夠訪問「該對象」的非同步代碼塊。
下面是「synchronized代碼塊」對應的演示程序。
orm

class Count { 
  
     // 含有synchronized同步塊的方法 
     public void synMethod() { 
         synchronized(this) { 
             try {  
                 for (int i = 0; i < 5; i++) { 
                     Thread.sleep(100); // 休眠100ms 
                     System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
                 }
             } catch (InterruptedException ie) {  
             }
         }  
     }
 
     // 非同步的方法
     public void nonSynMethod() {
         try {  
             for (int i = 0; i < 5; i++) {
                 Thread.sleep(100);
                 System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
             }
         } catch (InterruptedException ie) {  
         }
     }
 }
 
 public class Demo2 {
 
     public static void main(String[] args) {  
         final Count count = new Count();
         // 新建t1, t1會調用「count對象」的synMethod()方法33         Thread t1 = new Thread(
                 new Runnable() {
                     @Override
                     public void run() {
                         count.synMethod();
                     }
                 }, "t1");
 
         // 新建t2, t2會調用「count對象」的nonSynMethod()方法42         Thread t2 = new Thread(
                 new Runnable() {
                     @Override
                     public void run() {
                         count.nonSynMethod();
                     }
                 }, "t2");  
 
 
         t1.start();  // 啓動t1
         t2.start();  // 啓動t2
     } 
 }

運行結果

複製代碼

t1 synMethod loop 0t2 nonSynMethod loop 0t1 synMethod loop 1t2 nonSynMethod loop 1t1 synMethod loop 2t2 nonSynMethod loop 2t1 synMethod loop 3t2 nonSynMethod loop 3t1 synMethod loop 4t2 nonSynMethod loop 4

複製代碼

結果說明
主線程中新建了兩個子線程t1和t2。t1會調用count對象的synMethod()方法,該方法內含有同步塊;而t2則會調用count對象的nonSynMethod()方法,該方法不是同步方法。t1運行時,雖然調用synchronized(this)獲取「count的同步鎖」;可是並無形成t2的阻塞,由於t2沒有用到「count」同步鎖。

 

第三條

當一個線程訪問「某對象」的「synchronized方法」或者「synchronized代碼塊」時,其餘線程對「該對象」的其餘的「synchronized方法」或者「synchronized代碼塊」的訪問將被阻塞。
咱們將上面的例子中的nonSynMethod()方法體的也用synchronized(this)修飾。修改後的源碼以下:

class Count { 
 
    // 含有synchronized同步塊的方法 
    public void synMethod() { 
        synchronized(this) { 
            try {  
                for (int i = 0; i < 5; i++) { 
                    Thread.sleep(100); // 休眠100ms 
                    System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }

    // 也包含synchronized同步塊的方法
    public void nonSynMethod() {
        synchronized(this) {
            try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }
    }
}

public class Demo3 {

    public static void main(String[] args) {  
        final Count count = new Count();
        // 新建t1, t1會調用「count對象」的synMethod()方法35         Thread t1 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.synMethod();
                    }
                }, "t1");

        // 新建t2, t2會調用「count對象」的nonSynMethod()方法44         Thread t2 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.nonSynMethod();
                    }
                }, "t2");  


        t1.start();  // 啓動t1
        t2.start();  // 啓動t2
    } 
}

運行結果

複製代碼

t1 synMethod loop 0t1 synMethod loop 1t1 synMethod loop 2t1 synMethod loop 3t1 synMethod loop 4t2 nonSynMethod loop 0t2 nonSynMethod loop 1t2 nonSynMethod loop 2t2 nonSynMethod loop 3t2 nonSynMethod loop 4

複製代碼

結果說明
主線程中新建了兩個子線程t1和t2。t1和t2運行時都調用synchronized(this),這個this是Count對象(count),而t1和t2共用count。所以,在t1運行時,t2會被阻塞,等待t1運行釋放「count對象的同步鎖」,t2才能運行。

 

3. synchronized方法 和 synchronized代碼塊

synchronized方法」是用synchronized修飾方法,而 「synchronized代碼塊」則是用synchronized修飾代碼塊。

synchronized方法示例

public synchronized void foo1() {
    System.out.println("synchronized methoed");
}

synchronized代碼塊

public void foo2() {    synchronized (this) {
        System.out.println("synchronized methoed");
    }
}

synchronized代碼塊中的this是指當前對象。也能夠將this替換成其餘對象,例如將this替換成obj,則foo2()在執行synchronized(obj)時就獲取的是obj的同步鎖。


synchronized代碼塊能夠更精確的控制衝突限制訪問區域,有時候表現更高效率。下面經過一個示例來演示:

 // Demo4.java的源碼 
 public class Demo4 { 

   public synchronized void synMethod() { 
       for(int i=0; i<1000000; i++) 
           ; 
   } 

   public void synBlock() {
        synchronized( this ) {
            for(int i=0; i<1000000; i++)
                ;
        }
    }

    public static void main(String[] args) {
        Demo4 demo = new Demo4();

        long start, diff;
        start = System.currentTimeMillis();                // 獲取當前時間(millis)
        demo.synMethod();                                // 調用「synchronized方法」
        diff = System.currentTimeMillis() - start;        // 獲取「時間差值」
        System.out.println("synMethod() : "+ diff);
        
        start = System.currentTimeMillis();                // 獲取當前時間(millis)
        demo.synBlock();                                // 調用「synchronized方法塊」
        diff = System.currentTimeMillis() - start;        // 獲取「時間差值」
        System.out.println("synBlock()  : "+ diff);
    }
}

(某一次)執行結果

synMethod() : 11synBlock() : 3

 

4. 實例鎖 和 全局鎖

實例鎖 -- 鎖在某一個實例對象上。若是該類是單例,那麼該鎖也具備全局鎖的概念。
               實例鎖對應的就是synchronized關鍵字。
全局鎖 -- 該鎖針對的是類,不管實例多少個對象,那麼線程都共享該鎖。
               全局鎖對應的就是static synchronized(或者是鎖在該類的class或者classloader對象上)。

關於「實例鎖」和「全局鎖」有一個很形象的例子:

pulbic class Something {    public synchronized void isSyncA(){}    public synchronized void isSyncB(){}    public static synchronized void cSyncA(){}    public static synchronized void cSyncB(){}
}

假設,Something有兩個實例x和y。分析下面4組表達式獲取的鎖的狀況。
(01) x.isSyncA()與x.isSyncB() 
(02) x.isSyncA()與y.isSyncA()
(03) x.cSyncA()與y.cSyncB()
(04) x.isSyncA()與Something.cSyncA()

(01) 不能被同時訪問。由於isSyncA()和isSyncB()都是訪問同一個對象(對象x)的同步鎖!

// LockTest1.java的源碼 
class Something { 
    public synchronized void isSyncA(){ 
        try {  
            for (int i = 0; i < 5; i++) { 
                Thread.sleep(100); // 休眠100ms 
                System.out.println(Thread.currentThread().getName()+" : isSyncA"); 
            } 
        }catch (InterruptedException ie) {  
        }  
    }
    public synchronized void isSyncB(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : isSyncB");
            }
        }catch (InterruptedException ie) {  
        }  
    }
}

public class LockTest1 {

    Something x = new Something();
    Something y = new Something();

    // 比較(01) x.isSyncA()與x.isSyncB() 
    private void test1() {
        // 新建t11, t11會調用 x.isSyncA()
        Thread t11 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.isSyncA();
                    }
                }, "t11");

        // 新建t12, t12會調用 x.isSyncB()
        Thread t12 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.isSyncB();
                    }
                }, "t12");  


        t11.start();  // 啓動t11
        t12.start();  // 啓動t12
    }

    public static void main(String[] args) {
        LockTest1 demo = new LockTest1();
        demo.test1();
    }
}

運行結果

複製代碼

t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB

複製代碼

 

(02) 能夠同時被訪問。由於訪問的不是同一個對象的同步鎖,x.isSyncA()訪問的是x的同步鎖,而y.isSyncA()訪問的是y的同步鎖。

// LockTest2.java的源碼 
 class Something { 
     public synchronized void isSyncA(){ 
         try {  
             for (int i = 0; i < 5; i++) { 
                 Thread.sleep(100); // 休眠100ms 
                 System.out.println(Thread.currentThread().getName()+" : isSyncA"); 
             } 
         }catch (InterruptedException ie) {  
         }  
     }
     public synchronized void isSyncB(){
         try {  
             for (int i = 0; i < 5; i++) {
                 Thread.sleep(100); // 休眠100ms
                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
             }
         }catch (InterruptedException ie) {  
         }  
     }
     public static synchronized void cSyncA(){
         try {  
             for (int i = 0; i < 5; i++) {
                 Thread.sleep(100); // 休眠100ms
                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
             } 
         }catch (InterruptedException ie) {  
         }  
     }
     public static synchronized void cSyncB(){
         try {  
             for (int i = 0; i < 5; i++) {
                 Thread.sleep(100); // 休眠100ms
                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
             } 
         }catch (InterruptedException ie) {  
         }  
     }
 }
 
 public class LockTest2 {

   Something x = new Something();
    Something y = new Something();

    // 比較(02) x.isSyncA()與y.isSyncA()
    private void test2() {
        // 新建t21, t21會調用 x.isSyncA()
        Thread t21 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.isSyncA();
                    }
                }, "t21");

        // 新建t22, t22會調用 x.isSyncB()
        Thread t22 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        y.isSyncA();
                    }
                }, "t22");  


        t21.start();  // 啓動t21
        t22.start();  // 啓動t22
    }

    public static void main(String[] args) {
        LockTest2 demo = new LockTest2();

        demo.test2();
    }
}

運行結果

複製代碼

t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA

複製代碼

 

(03) 不能被同時訪問。由於cSyncA()和cSyncB()都是static類型,x.cSyncA()至關於Something.isSyncA(),y.cSyncB()至關於Something.isSyncB(),所以它們共用一個同步鎖,不能被同時反問。

複製代碼

// LockTest3.java的源碼 
class Something { 
    public synchronized void isSyncA(){ 
        try {  
            for (int i = 0; i < 5; i++) { 
                Thread.sleep(100); // 休眠100ms 
                System.out.println(Thread.currentThread().getName()+" : isSyncA"); 
            } 
        }catch (InterruptedException ie) {  
        }  
    }
    public synchronized void isSyncB(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : isSyncB");
            }
        }catch (InterruptedException ie) {  
        }  
    }
    public static synchronized void cSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {24                 Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : cSyncA");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
    public static synchronized void cSyncB(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : cSyncB");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
}

public class LockTest3 {

    Something x = new Something();
    Something y = new Something();

    // 比較(03) x.cSyncA()與y.cSyncB()
    private void test3() {
        // 新建t31, t31會調用 x.isSyncA()
        Thread t31 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.cSyncA();
                    }
                }, "t31");

        // 新建t32, t32會調用 x.isSyncB()
        Thread t32 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        y.cSyncB();
                    }
                }, "t32");  


        t31.start();  // 啓動t31
        t32.start();  // 啓動t32
    }

    public static void main(String[] args) {
        LockTest3 demo = new LockTest3();

        demo.test3();
    }
}

複製代碼

運行結果

複製代碼

t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB

複製代碼

 

(04) 能夠被同時訪問。由於isSyncA()是實例方法,x.isSyncA()使用的是對象x的鎖;而cSyncA()是靜態方法,Something.cSyncA()能夠理解對使用的是「類的鎖」。所以,它們是能夠被同時訪問的。

// LockTest4.java的源碼 
class Something { 
    public synchronized void isSyncA(){ 
        try {  
            for (int i = 0; i < 5; i++) { 
                Thread.sleep(100); // 休眠100ms 
                System.out.println(Thread.currentThread().getName()+" : isSyncA"); 
            } 
        }catch (InterruptedException ie) {  
        }  
    }
    public synchronized void isSyncB(){
        try {  
            for (int i = 0; i < 5; i++) {1
               Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : isSyncB");
            }
        }catch (InterruptedException ie) {  
        }  
    }
    public static synchronized void cSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : cSyncA");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
    public static synchronized void cSyncB(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms3
               System.out.println(Thread.currentThread().getName()+" : cSyncB");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
}

public class LockTest4 {

    Something x = new Something();
    Something y = new Something();

    // 比較(04) x.isSyncA()與Something.cSyncA()
    private void test4() {
        // 新建t41, t41會調用 x.isSyncA()
        Thread t41 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        x.isSyncA();
                    }
                }, "t41");

        // 新建t42, t42會調用 x.isSyncB()
        Thread t42 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Something.cSyncA();
                    }
                }, "t42");  


        t41.start();  // 啓動t41
        t42.start();  // 啓動t42
    }

    public static void main(String[] args) {
        LockTest4 demo = new LockTest4();

        demo.test4();
    }
}

運行結果

複製代碼

t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA

複製代碼

相關文章
相關標籤/搜索