java併發程式設計——執行緒同步

ahiru?發表於2019-02-01

sysynchronized關鍵字可以修飾方法、程式碼塊,但不能修飾構造器、成員變數等。

當sysynchronized關鍵字同來修飾方法和程式碼塊時,能夠保證同一時刻最多隻有一個執行緒執行該段程式碼或方法。防止當有兩個執行緒併發修改同一個檔案時可能會造成異常。

同步程式碼塊語法:

    synchronized(obj){
        ...
        //此處程式碼是同步程式碼塊
    }
複製程式碼

意思為:當執行緒要執行以下程式碼塊之前,必須先獲得對obj的鎖。

當兩個併發執行緒訪問同一個物件object中的synchronized(this)同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完程式碼塊以後才能執行該程式碼塊。

然而,另一個當一個執行緒訪問object的一個synchronized(this)程式碼塊時,另一個執行緒仍然可以訪問該object中的非synchronized(this)同步程式碼塊,但是其他執行緒對object中其他所有的synchronized(this)同步程式碼塊的訪問將被阻塞

示例:

public class Thread1 {

    public void synchronize() {
        synchronized (this) {
            System.out.println("I am the synchronized part:");
            for (int i=0; i<3; i++) {
                System.out.println(Thread.currentThread().getName()+":"+(i+1));
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("the synchronized part is over");
        }
    }

    public void synchronize2() {
        synchronized (this) {
            System.out.println("I am the synchronized part 2:");
            for (int i=0; i<3; i++) {
                System.out.println(Thread.currentThread().getName()+":"+(i+1));
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("the synchronized part 2 is over");
        }
    }

    public void nonSynchronize() {
        System.out.println("non-synchronized part:");
        for (int i=0; i<3; i++) {
            System.out.println(Thread.currentThread().getName()+":"+(i+1));
            try {
                Thread.sleep(500);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("the non-synchronized part is over");
    }

    public static void main(String[] args) {
        final Thread1 thread1 = new Thread1();

    //啟動執行緒,執行緒1、2和3分別用了不同的表示式
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                thread1.synchronize();
            }
        });
        Thread t2 = new Thread(() -> thread1.synchronize2());
        Thread t3 = new Thread(thread1::nonSynchronize);
        t1.start();
        t2.start();
        t3.start();
    }
}複製程式碼

輸出結果:

java併發程式設計——執行緒同步

從輸出結果可以看出:執行緒0執行的是第一個同步程式碼塊,執行緒2執行的是非同步程式碼塊,執行緒0和2同時執行,兩者互不干擾。執行緒1執行的是第二個同步程式碼塊,因為執行緒0先獲得對該物件的鎖定,所以執行緒1即使執行程式碼塊跟執行緒0不一樣,也被阻塞,必須等執行緒1執行完同步程式碼塊,釋放鎖,執行緒1才能繼續執行。

同步方法示例

public class Thread1 {

    private synchronized void synchronize() {
        System.out.println("I am the synchronized method part:");
        for (int i=0; i<3; i++) {
            System.out.println(Thread.currentThread().getName()+":"+(i+1));
            try {
                Thread.sleep(500);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("the synchronized method part is over");
    }

    private synchronized void synchronize2() {
        System.out.println("I am the synchronized method part 2:");
        for (int i=0; i<3; i++) {
            System.out.println(Thread.currentThread().getName()+":"+(i+1));
            try {
                Thread.sleep(500);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("the synchronized method part 2 is over");
    }

    private  void nonSynchronize() {
        System.out.println("non-synchronized method part:");
        for (int i=0; i<3; i++) {
            System.out.println(Thread.currentThread().getName()+":"+(i+1));
            try {
                Thread.sleep(500);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("the non-synchronized method part is over");
    }

    public static void main(String[] args) {
        final Thread1 thread1 = new Thread1();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                thread1.synchronize();
            }
        });
        Thread t2 = new Thread(() -> thread1.synchronize2());
        Thread t3 = new Thread(thread1::nonSynchronize);
        t1.start();
        t2.start();
        t3.start();
    }
}複製程式碼

輸出結果跟同步程式碼塊一樣:

java併發程式設計——執行緒同步

同步鎖

Lock 是控制多個執行緒對共享資源進行訪問的工具。每次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應先獲得Lock物件。

在實現執行緒安全的控制中,比較常用的是ReentrantLock。

class X {
    private final ReentrantLock lock = new ReentrantLock();
    //定義需要保證執行緒安全的方法
    public void m() {
        //加鎖
        lock.lock();
        try {
            //method body
        } finally { //使用finally來保證釋放鎖
            lock.unlock();
        }
    }
}複製程式碼


參考資料:java加鎖與同步方法

瘋狂java講義(第三版)

Java程式設計思想(第4版)


相關文章