1. 程式人生 > >Java的高併發程式設計系列(一)synchronized鎖

Java的高併發程式設計系列(一)synchronized鎖

private int count = 10;
public void test(){
    synchronized (this) { //任何執行緒要執行下面的程式碼,必須先拿到Demo02物件例項的鎖
            count --;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
public synchronized void test(){//等同於synchronized(this),鎖定的是Demo03物件的例項
        count --;
        System.out
.println(Thread.currentThread().getName() + " count =" + count); }

synchronized是對當前物件Object加了一把鎖,在當前執行緒使用過程中其他執行緒無法呼叫,只有上一個執行緒釋放鎖後,其他執行緒才可以使用,所以synchronized是互斥鎖。在這裡需要注意的是,snchronized鎖定的是一個物件,而不是一個類或者程式碼,這個物件是可以自己自定義的,這裡是用this鎖定了自己。以上兩個等同。

public static void test2(){ //考慮一下這裡寫synchronize(this)是否可以
synchronized (Demo04.class) { count --; } }

synchronized當鎖定靜態方法時,那麼此時鎖定的當前類的class。

public class Demo05 implements Runnable{
    private int count = 10;
    @Override
    public /*synchronized*/ void run(){
        count --;
        System.out.println(Thread.currentThread().getName() + " count = "
+ count); } public static void main(String[] args) { Demo05 demo05 = new Demo05(); for (int i = 0; i < 5; i++) { new Thread(demo05,"THREAD" + i).start(); } } }

以上程式碼Demo05執行緒,當定義5個執行緒時,共同呼叫count,造成的重用,無法進行資源的共享,當新增 synchronized 時,對run方法加了把鎖,一次只能一個執行緒進行呼叫,不允許其他執行緒訪問,可避免count 的重用,執行緒的重入。synchronized修飾的程式碼塊是原子操作。

注意:在多執行緒中,同步方法和非同步方法時可以同時呼叫的,這兩者之間互不影響。

案例:當對業務寫方法加鎖,而不對讀方法加鎖時,容易產生髒讀問題。

public class Demo08 {
    String name;
    double balance;
    public synchronized void set(String name, double balance){
        this.name = name;
        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public synchronized double getBalance(String name){
        return this.balance;
    }
    pblic static void main(String[] args) {
        Demo08 demo08 = new Demo08();
        new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(demo08.getBalance("zhangsan"));
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(demo08.getBalance("zhangsan"));
    }
}

在某些業務情況下,當對讀取的資料要求不高時,允許髒讀的情況下,可以不使用synchronized的情況下,提升效能。

一個同步方法可以呼叫另一個同步方法,一個執行緒已經擁有某個物件的鎖,再次申請的時候 仍然會得到該物件的鎖也就是說synchronized獲得的鎖是可重入的。

public class Demo09 {
    synchronized void test1(){
        System.out.println("test1 start.........");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        test2();
    }
    synchronized void test2(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test2 start.......");
    }
    public static void main(String[] args) {
        Demo09 demo09 = new Demo09();
        demo09.test1();
    }
}

繼承中有可能發生的情形,子類呼叫父類的同步方法

public class Demo10 {
    synchronized void test(){
        System.out.println("test start........");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test end........");
    }

    public static void main(String[] args) {
        new Demo100().test();
    }
}
class Demo100 extends Demo10{
    @Override
    synchronized void test() {
        System.out.println("child test start.......");
        super.test();
        System.out.println("child test end.......");
    }
}

總而言之:synchronized修飾的鎖是可重入的,可以被其他synchronized方法呼叫。

死鎖:簡而言之,假如有一個執行緒需要依次鎖定A和B,另一個執行緒需要依次鎖定B和A,此時,第一個執行緒無法鎖定B,第二個執行緒無法鎖定A,這樣就產生了死鎖。

程式在執行過程中,如果出現異常,預設情況鎖會被釋放,所以,在併發處理的過程中,有異常要多加小心,不然可能會發生不一致的情況。比如,在一個web app處理過程中,多個servlet執行緒共同訪問通一個資源,這是如果異常處理不合適,在第一個執行緒中丟擲異常,其他執行緒就會進入同步程式碼去,有可能訪問到異常產生是的資料,因此要非常小心的處理同步業務邏輯中的異常。

public class Demo11 {
    int count = 0;
    synchronized void test(){
        System.out.println(Thread.currentThread().getName() + " start......");
        while (true) {
            count ++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5) {
                int i = 1/0; //此處丟擲異常,鎖將被釋放,要想不被釋放,可以在這裡進行catch處理,然後讓迴圈繼續
            }
        }
    }
    public static void main(String[] args) {
        Demo11 demo11 = new Demo11();
        Runnable r = new Runnable() {   
            @Override
            public void run() {
                demo11.test();
            }
        };
        new Thread(r, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }   
        new Thread(r, "t2").start();
    }
}