1. 程式人生 > >Java多執行緒之基礎篇(一)

Java多執行緒之基礎篇(一)

一、併發和並行

1.1 概念

  • 並行:多個CPU例項或是多臺機器同時執行一段處理邏輯,是真正的同時。
  • 併發:通過CUP排程演算法,讓使用者看上去同時去執行,實際上從CPU操作層面並不是真正的同時。併發往往需要公共的資源,對公共資源的處理和執行緒之間的協調是併發的難點。

1.2 比較

  如果你想要一個程式執行的更快,那麼可以將其斷開為多個片段,在單獨的處理器上執行每個片段。併發是用於多處理器程式設計的基本工具。為了使程式執行得更快,你必須學習如何利用這些額外的處理器,而則正是併發賦予你的能力。
  如果你有一臺多處理器的機器,那麼就可以在這些處理器之間分部多個任務,從而可以極大的提高吞吐量。這是使用強有力的多處理器Web伺服器的常見情況,在為每一個請求分配一個執行緒的程式中,它可以將大量的使用者請求分佈到多個CPU上。
  但是,併發通常是提高執行在單處理器上的程式的效能。
  因為,在單處理器上執行的併發程式開銷應該比改程式的所有部分都順序執行的開銷大,因為其中增加了所謂的上下文切換的代價(從一個任務切換到另一個任務)。表面上看,將程式的所有部分當作單個的任務執行好像是開銷更小一點,並且可以節省上下文切換的代價。
  使得這個問題變得有些不同的是阻塞。如果程式中的某個任務因為該程式控制範圍之外的某繫條件(通常是I/O)而導致不能繼續執行,那麼我們就說這個任務或現場阻塞了。如果沒有併發,則整個程式都將停止下來,直到外部條件發生變化。但是,如果使用併發來編寫程式,那麼當一個任務阻塞時,程式中的其他任務還可以繼續執行,因此這個程式可以保持繼續向前執行。事實上,從效能的角度看,如果沒有任務阻塞,那麼在單處理器上使用併發就沒有任何意義。
  在單處理器系統中的效能提高的常見例項是事件驅動的程式設計。實際上,使用併發最吸引人的一個原因就是要產生具有可響應的使用者介面。這也是在Android系統中不允許子執行緒總訪問UI的原因,在Android中的UI控制元件不是執行緒安全的,如果在多執行緒中併發訪問可能會導致UI控制元件處於不可預期的狀態。那為什麼系統不對UI控制元件的訪問加上鎖機制呢?原因有兩個:其一,加上鎖會讓UI訪問的邏輯變的複雜;其二,鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些執行緒的執行。所以在Android系統中最簡單而且高效的方法就是採用單執行緒模型來處理UI操作。

1.3 程序和執行緒

  實現併發最直接的方式是在作業系統級別使用程序。程序是執行在它自己的地址空間內的自包容的程式。多工作業系統可以通過週期性地將CPU從一個程序切換到另一個程序,來實現同時執行多個(程序)程式,儘管這使得每個程序看起來在其執行過程中都是歇歇停停。程序被作業系統互相隔開,因此不會彼此干涉,這使得用程序程式設計相對容易一些。與此相反,像Java所使用的這種併發系統會共享諸如記憶體和I/O這樣的資源,因此程式設計編寫多執行緒程式最基本的困難在於,協調不同執行緒驅動的任務之間對這些資源的使用,以便使得這些資源不會同時被多個任務訪問。

二、基礎概念

2.1 執行緒狀態圖

執行緒狀態圖

執行緒包括5種狀態:
1、新建(New):執行緒物件被建立時,它只會短暫地處於這種狀態。此時它已經分配了必須的系統資源,並執行了初始化。例如,Thread thread = new Thread()。
2、就緒(Runnable):稱為“可執行狀態”。執行緒物件被建立後,其它執行緒呼叫了該物件的start()方法,從而來啟動該執行緒。例如,thread.start()。處於就緒狀態的執行緒,隨時可能被CPU排程執行。
3、執行(Running):執行緒獲取CPU許可權進行執行。注意:執行緒只能從就緒狀態進入執行狀態。
4、阻塞(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分為三種:
(1)等待阻塞:通過呼叫執行緒的wait()方法,讓執行緒等待某工作的完成。
(2)同步阻塞:執行緒在獲取synchronized同步鎖失敗(因為鎖被其他執行緒佔用),它會進入同步阻塞狀態。
(3)其他阻塞:通過呼叫執行緒的sleep()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或是超時。或是I/O處理完畢時,執行緒重新轉入就緒狀態。
5.死亡(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

2.2 關鍵函式和關鍵字

這5中狀態涉及到的內容包括Object類,Thread類和synchronized關鍵字。
在Object類中,定義了wait(),notify(),notifyAll()等一系列休眠/喚醒函式。
在Thread類中,定義了一系列執行緒操作函式,例如,sleep()休眠函式,interrupt中斷()函式,getName()獲取執行緒名稱等。
synchronized關鍵字,它是區分synchronized程式碼塊和synchronized方法。synchronized的作用是讓執行緒獲取物件的同步鎖。

三、常用的實現多執行緒的兩種方式

3.1 概述

常用的實現多執行緒的兩種方式:Thread和Runnable。之所以說是“常用”,是因為在Java 5後可以通過java.util.concurrent包中的執行緒池來實現多執行緒。關於多線池的內容我會在java多執行緒之進階篇中介紹。Runnable是一個介面,該介面包含了一個run()方法,Runnable具有更好的擴充套件性。Thread是一個類,Thread本身就是實現了Runnable介面。此外,Runnable還可以用於“資源共享”。即,多個執行緒都是基於某個Runnable物件建立的,它們會共享Runnable物件上的資源

3.2 實現多執行緒例項

3.2.1 定義任務(Runnable)

執行緒可以驅動任務,因此你需要一種描述任務的方式,在Java中用Runnable介面來提供。需要定義任務,只需要實現Runnable介面並編寫run()方法,使得該任務可以執行你的命令。

public class RunnableTest {

    /**
     * @param args
     */
    public static void main(String[] args) {

        class MyRunnable implements Runnable{
            private int ticket=10; 
            @Override
            public void run() {
                for(int i=0;i<20;i++){ 
                    if(this.ticket>0){
                        System.out.println(Thread.currentThread().getName()+" 賣票:ticket "+this.ticket--);
                    }
                }

            }

        }

        // 啟動3個執行緒t1,t2,t3(它們共用一個Runnable物件),這3個執行緒一共賣10張票!這說明它們是共享了MyRunnable介面的。
        MyRunnable runnable = new MyRunnable();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        t1.start();
        t2.start();
        t3.start();
    }

}

執行結果:

Thread-0 賣票:ticket 10
Thread-2 賣票:ticket 8
Thread-1 賣票:ticket 9
Thread-2 賣票:ticket 6
Thread-0 賣票:ticket 7
Thread-0 賣票:ticket 3
Thread-0 賣票:ticket 2
Thread-0 賣票:ticket 1
Thread-2 賣票:ticket 4
Thread-1 賣票:ticket 5

3.2.2 繼承Thread

將Runnable物件轉變為工作任務的傳統方式是把它提交給一個Thread構造器。

public class ThreadTest {

    /**
     * @param args
     */
    public static void main(String[] args) {

        class MyThread extends Thread{
            private int ticket = 10;
            public  void run(){
                    for(int i=0;i<20;i++){
                        if(this.ticket>0){
                            System.out.println(this.getName()+"買票:ticket "+this.ticket--);
                        }
                    }               
            }
        }

        // 啟動3個執行緒t1,t2,t3;每個執行緒各賣10張票!
        // 和上面的結果對比,並揣摩 “Runnable還可以用於“資源共享”。即,多個執行緒都是基於某個Runnable物件建立的,它們會共享Runnable物件上的資源”這句話。
        MyThread t1 =new MyThread(); 
        MyThread t2 =new MyThread(); 
        MyThread t3 =new MyThread(); 
        t1.start();
        t2.start();
        t3.start();
    }
}

執行結果:

Thread-0買票:ticket 10
Thread-2買票:ticket 10
Thread-1買票:ticket 10
Thread-2買票:ticket 9
Thread-0買票:ticket 9
Thread-2買票:ticket 8
Thread-1買票:ticket 9
Thread-2買票:ticket 7
Thread-0買票:ticket 8
Thread-2買票:ticket 6
Thread-1買票:ticket 8
Thread-1買票:ticket 7
Thread-2買票:ticket 5
Thread-0買票:ticket 7
Thread-2買票:ticket 4
Thread-1買票:ticket 6
Thread-2買票:ticket 3
Thread-0買票:ticket 6
Thread-2買票:ticket 2
Thread-1買票:ticket 5
Thread-2買票:ticket 1
Thread-0買票:ticket 5
Thread-1買票:ticket 4
Thread-0買票:ticket 4
Thread-1買票:ticket 3
Thread-0買票:ticket 3
Thread-1買票:ticket 2
Thread-0買票:ticket 2
Thread-1買票:ticket 1
Thread-0買票:ticket 1

四、synchronized關鍵字

4.1 概述

  synchronized關鍵字是為了解決共享資源競爭的問題,共享資源一般是以物件形式存在的記憶體片段,但也可以是檔案、輸入/輸出埠,或者是印表機。要控制對共享資源的訪問,得先把它包裝進一個物件。然後把所有要訪問的這個資源的方法標記為synchronized。如果某個任務處於一個對標記為synchronized的方法的呼叫中,那麼在這個執行緒從該方法返回之前,其他所有要呼叫類中任何標記為synchronized方法的執行緒都會被阻塞。所有物件都自動含有單一的鎖(也稱為監視器)。當在物件上呼叫其任意synchronized方法的時候,物件都被加鎖,這時該物件上的其他synchronized方法只有等到前一個方法呼叫完畢並釋放了鎖之後才能被呼叫。
  在Java中,每個物件有且僅有一個同步鎖。這也意味著,同步鎖是依賴於物件而存在的。當我們呼叫某個物件的synchronized方法時,就獲得了該物件的同步鎖,不同執行緒對同步鎖的訪問是互斥的。因為鎖語句產生了一種互相排斥的效果,所以這種機制常常稱為互斥量(mutex)

4.2 synchronized基本原則和例項

4.2.1 基本原則

我們將synchronized的基本規則總結為下面3條,並通過例項對它們進行說明。
第一條:當一個執行緒訪問某物件synchronized方法或者synchronized程式碼塊時,其他執行緒對該物件的該synchronized方法或者synchronized程式碼塊的訪問將被阻塞。
第二條:當一個執行緒訪問某物件synchronized方法或者synchronized程式碼塊時,其他執行緒仍然可以訪問該物件的非同步程式碼塊。
第三條:當一個執行緒訪問某物件synchronized方法或者synchronized程式碼塊時,其他執行緒對該物件的其他的synchronized方法或者synchronized程式碼塊的訪問將被阻塞。

4.2.2 例項

第一條:當一個執行緒訪問某物件synchronized方法或者synchronized程式碼塊時,其他執行緒對該物件的該synchronized方法或者synchronized程式碼塊的訪問將被阻塞。

//例項1
public class RunnableTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        class MyRunnable implements Runnable{

            private int j=5;
            @Override
            public void run() {

                synchronized(this){
                    for(int i=0;i<5;i++){
                        try {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName()+" loop "+i);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }   
            }   
        }
        Runnable runnable = new MyRunnable();
        Thread t1 = new Thread(runnable,"t1");
        Thread t2 = new Thread(runnable,"t2");

        t1.start();
        t2.start();     
    }
}

執行結果:

t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t1 loop 5
t1 loop 6
t1 loop 7
t1 loop 8
t1 loop 9
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
t2 loop 5
t2 loop 6
t2 loop 7
t2 loop 8
t2 loop 9

結果說明:run()方法中存在synchronized(this)程式碼塊,而且t1和t2都是基於MyRunnable這個Runnable物件建立的執行緒。這就意味著,我們可以將synchronized(this)中的this看做是MyRunnable這個Runnable物件;因此,執行緒t1和t2共享“MyRunable物件的同步鎖”。所以,當一個執行緒執行的時候,另外一個執行緒必須等待正在執行的執行緒釋放MyRunnable的同步鎖之後才能執行。


下面一個極其相似的例子:

//例項2
public class ThreadTest {

    /**
     * @param args
     */
    public static void main(String[] args) {

        class MyThread extends Thread{
            public MyThread(String name){
                super(name);
            }
            @Override
            public void run() {
                synchronized(this){
                    for(int i=0;i<10;i++){
                        try {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName()+" loop "+i);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        Thread t1 = new MyThread("t1");
        Thread t2 = new MyThread("t2");
        t1.start();
        t2.start();
    }
}

執行結果:

t2 loop 0
t1 loop 0
t2 loop 1
t1 loop 1
t1 loop 2
t2 loop 2
t2 loop 3
t1 loop 3
t1 loop 4
t2 loop 4
t2 loop 5
t1 loop 5
t1 loop 6
t2 loop 6
t1 loop 7
t2 loop 7
t2 loop 8
t1 loop 8
t2 loop 9
t1 loop 9

對比結果,你是否可以正確且快速的明白呢?上面的例項1是實現了Runnable介面,例項2繼承了Thread類。在run()方法中都有synchronized(this),例項1的結果是先執行執行緒t1然後才是執行緒t2,例項2的結果是執行緒t1和t2交替執行。
分析:synchronized(this)中的this是指當前物件,即synchronized(this)所在類對應的當前物件。它的作用是獲取獲取當前物件的同步鎖。對於例項2中的synchronized(this)中的this代表的是MyThread物件,t1和t2是兩個不同的MyThread物件,因此t1和t2在執行synchronized(this)時獲取的是不同物件的同步鎖。對於例項1來說,synchronized(this)中的this代表的時候MyRunnable物件,t1和t2是共同一個MyRunnable物件,因此,一個執行緒獲取了物件的同步鎖,會造成另一個執行緒的等待。


第二條:當一個執行緒訪問某物件synchronized方法或者synchronized程式碼塊時,其他執行緒仍然可以訪問該物件的非同步程式碼塊。

public class SyncAndNoSync {

    /**
     * @param args
     */
    public static void main(String[] args) {

        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) {  
                }
            }
        }

        final Count count = new Count();
        // 新建t1, t1會呼叫“count物件”的synMethod()方法
        Thread t1 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.synMethod();
                    }
                }, "t1");

        // 新建t2, t2會呼叫“count物件”的nonSynMethod()方法
        Thread t2 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.nonSynMethod();
                    }
                }, "t2");  

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

}

執行結果:

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

結果說明:
執行緒t1和t2交替執行。t1會呼叫count物件的synMethod()方法,該方法中含有同步塊;而t2則會呼叫count物件的nonSynMethod()方法,該方法不是同步方法。t1執行時,雖然呼叫synchronized(this)獲取count物件的同步鎖;但是並沒有造成t2的阻塞,因為t2沒有用到count物件的同步鎖


第三條:當一個執行緒訪問某物件synchronized方法或者synchronized程式碼塊時,其他執行緒對該物件的其他的synchronized方法或者synchronized程式碼塊的訪問將被阻塞。

public class SyncAndSync {

    /**
     * @param args
     */
    public static void main(String[] args) {
        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 synMethod2() {
                synchronized(this) {
                    try {  
                        for (int i = 0; i < 5; i++) {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName() + " synMethod2 loop " + i);  
                        }
                    } catch (InterruptedException ie) {  
                    }
                }
            }
        }

        final Count count = new Count();

        // 新建t1, t1會呼叫“count物件”的synMethod()方法
        Thread t1 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.synMethod();
                    }
                }, "t1");

        // 新建t2, t2會呼叫“count物件”的synMethod2()方法
        Thread t2 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        count.synMethod2();
                    }
                }, "t2");  
        t1.start();  // 啟動t1
        t2.start();  // 啟動t2
    }

}

執行結果:

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

結果說明:
t1和t2執行時都呼叫synchronized(this),這個this是Count物件(count),而t1和t2共用count。因此,在t1執行時,t2會被阻塞,等待t1執行釋放“count物件的同步鎖”,t2才能執行。

4.3 synchronized方法和synchronized程式碼塊

4.3.1 概述

  synchronized方法是用synchronized修飾方法,這是一種粗粒度鎖;這個同步方法(非static方法)無需顯式指定同步監視器,同步方法的同步監視器是this,也就是呼叫該方法的物件。
  synchronized程式碼塊是用synchronized修飾程式碼塊,這是一種細粒度鎖。執行緒開始執行同步程式碼塊之前,必須先獲得對同步監視器的鎖定,任何時候只能有一個執行緒可以獲得對同步監視器的鎖定,當同步程式碼塊執行完成後,該執行緒會釋放對同步監視器的鎖定。雖然Java允許使用任何物件作為同步監視器,但同步監視器的目的就是為了阻止兩個執行緒對同一個共享資源進行併發訪問,因此通常推薦使用可能被併發訪問的共享資源充當同步監視器。

4.3.2 例項

public class SnchronizedTest {

    public static void main(String[] args) {

        class Demo {

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

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

        Demo demo = new Demo();
        long start,diff;
        start = System.currentTimeMillis();
        demo.synMethod();                                 // 呼叫“synchronized方法塊”
        diff = System.currentTimeMillis() - start;        // 獲取“時間差值”
        System.out.println("synMethod() : "+ diff);


        start = System.currentTimeMillis();              
        demo.synBlock();                                // 呼叫“synchronized方法塊”
        diff = System.currentTimeMillis() - start;      // 獲取“時間差值”
        System.out.println("synBlock()  : "+ diff);
    }

}

執行結果:

synMethod() : 24
synBlock()  : 9

結果說明:synchronized程式碼塊可以更精確的控制衝突限制訪問區域,有時候表現更高效率。

4.4 例項鎖和全域性鎖

4.4.1 概念

  例項鎖:鎖在某個例項物件上。如果該類是單例,那麼該鎖也是具有全域性鎖的概念。例項鎖對應的就是synchronized關鍵字。
  全域性鎖:該鎖針對的是類,無論例項多少個物件,那麼執行緒都共享該鎖。全域性鎖對應的就是static synchronized(或者是鎖在該類的class或者classloader物件上)。

4.4.2 例項

關於例項鎖全域性鎖有個很好的例子。

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()

4..4.2.1 x.isSyncA()與x.isSyncB()不能同時訪問

這是因為isSyncA()和isSyncB()都是訪問的同一個物件(物件x)的同步鎖。

public class LockTest {

    static class Something{

        public synchronized void isSyncA(){
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+" : isSyncA");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        public synchronized void isSyncB(){
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+" : isSyncB");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

         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) {  
                }  
            }
    }

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

        private void test1(){

            Thread t11 = new Thread(new Runnable(){
                @Override
                public void run() {
                    x.isSyncA();    
                    //x.cSyncA();
                }
            },"t11");

            Thread t12 = new Thread(new Runnable(){
                @Override
                public void run() {
                    x.isSyncB();
                    //y.isSyncA();
                    //y.cSyncB();
                    //Something.cSyncA();
                }
            },"t12");

            t11.start();
            t12.start();        
        }
    public static void main(String[] args) {
        LockTest lockTest1 = new LockTest();
        lockTest1.test1();
    }

}

執行結果:

t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
4..4.2.2 x.isSyncA()與y.isSyncB()能同時訪問

因為訪問的不是同一個物件的同步鎖,x.isSyncA()訪問的是x的同步鎖,而y.isSyncA()訪問的是y的同步鎖。
程式碼只需要在4.4.2.1上的程式碼將x.isSyncA() 和y.isSyncA()的註釋去掉,其他的語句加上註釋。
執行結果:

t11 : isSyncA
t12 : isSyncA
t11 : isSyncA
t12 : isSyncA
t12 : isSyncA
t11 : isSyncA
t12 : isSyncA
t11 : isSyncA
t12 : isSyncA
t11 : isSyncA
4..4.2.3 x.cSyncA()與y.cSyncB() 不能同時被訪問

因為cSyncA()和cSyncB()都是static型別,x.cSyncA()相當於Something.isSyncA(),y.cSyncB()相當於Something.isSyncB(),因此它們共用一個同步鎖,不能被同時反問。
程式碼只需要在4.4.2.1上的程式碼將 x.cSyncA()與y.cSyncB() 的註釋去掉,其他的語句加上註釋。
執行結果:

t12 : cSyncB
t12 : cSyncB
t12 : cSyncB
t12 : cSyncB
t12 : cSyncB
t11 : cSyncA
t11 : cSyncA
t11 : cSyncA
t11 : cSyncA
t11 : cSyncA
4..4.2.4 x.isSyncA()與Something.cSyncA() 可以被同時訪問

因為isSyncA()是例項方法,x.isSyncA()使用的是物件x的鎖;而cSyncA()是靜態方法,Something.cSyncA()可以理解對使用的是“類的鎖”。因此,它們是可以被同時訪問的。
程式碼只需要在4.4.2.1上的程式碼將 x.isSyncA()與Something.cSyncA() 的註釋去掉,其他的語句加上註釋。
執行結果:

t12 : cSyncA
t11 : isSyncA
t11 : isSyncA
t12 : cSyncA
t11 : isSyncA
t12 : cSyncA
t11 : isSyncA
t12 : cSyncA
t11 : isSyncA
t12 : cSyncA

估計這一篇有點太長了,如果你有興趣,可以接著看Java多執行緒之基礎篇(二)
站在巨人的肩膀上:《Java程式設計思想》和 大神之作