1. 程式人生 > >Android多執行緒-----併發和同步(synchronized)

Android多執行緒-----併發和同步(synchronized)

一、鎖

物件的內建鎖和物件的狀態之間是沒有內在的關聯的,雖然大多數類都將內建鎖用做一種有效的加鎖機制,但物件的域並不一定通過內建鎖來保護。當獲取到與物件關聯的內建鎖時,並不能阻止其他執行緒訪問該物件,當某個執行緒獲得物件的鎖之後,只能阻止其他執行緒獲得同一個鎖。之所以每個物件都有一個內建鎖,是為了免去顯式地建立鎖物件。

所以synchronized只是一個內建鎖的加鎖機制,當某個方法加上synchronized關鍵字後,就表明要獲得該內建鎖才能執行,並不能阻止其他執行緒訪問不需要獲得該內建鎖的方法

java內建鎖是一個互斥鎖,這就是意味著最多隻有一個執行緒能夠獲得該鎖,當執行緒A嘗試去獲得執行緒B持有的內建鎖時,執行緒A必須等待或者阻塞,知道執行緒B釋放這個鎖,如果B執行緒不釋放這個鎖,那麼A執行緒將永遠等待下去,這對高併發的系統是致命的

java的物件鎖和類鎖:java的物件鎖和類鎖在鎖的概念上基本上和內建鎖是一致的,但是,兩個鎖實際是有很大的區別的,物件鎖是用於物件例項方法,或者一個物件例項上的,類鎖是用於類的靜態方法或者一個類的class物件上的。我們知道,類的物件例項可以有很多個,但是每個類只有一個class物件,所以不同物件例項的物件鎖是互不干擾的,但是每個類只有一個類鎖。

Java中每一個物件都可以作為鎖,這是synchronized實現同步的基礎: 
1. 普通同步方法,鎖是當前例項物件 
2. 靜態同步方法,鎖是當前類的class物件 
3. 同步方法塊,鎖是括號裡面的物件

 

下面講解

synchronized修飾方法,

synchronized修飾靜態方法,

synchronized(this),

synchronized(類),

synchronized(變數)之間的區別

 

二、修飾方法和程式碼塊

修飾方法是在方法的前面加synchronized,synchronized修飾方法和修飾一個程式碼塊類似,只是作用範圍不一樣,修飾程式碼塊是大括號括起來的範圍,而修飾方法範圍是整個函式。


public synchronized void method()
{
   // todo
}

public void method()
{
   synchronized(this) {
      // todo
   }
}

雖然可以使用synchronized來定義方法,但synchronized並不屬於方法定義的一部分,因此,synchronized關鍵字不能被繼承。如果在父類中的某個方法使用了synchronized關鍵字,而在子類中覆蓋了這個方法,在子類中的這個方法預設情況下並不是同步的,而必須顯式地在子類的這個方法中加上synchronized關鍵字才可以。當然,還可以在子類方法中呼叫父類中相應的方法,這樣雖然子類中的方法不是同步的,但子類呼叫了父類的同步方法,因此,子類的方法也就相當於同步了。這兩種方式的例子程式碼如下: 
在子類方法中加上synchronized關鍵字

class Parent {
   public synchronized void method() { }
}
class Child extends Parent {
   public synchronized void method() { }
}

注意:

  1. 在定義介面方法時不能使用synchronized關鍵字。
  2. 構造方法不能使用synchronized關鍵字,但可以使用synchronized程式碼塊來進行同步。 

(一)物件鎖的synchronized修飾方法和程式碼塊

public class TestSynchronized {
    public void test1() {
        synchronized (this) {
            int i = 5;
            while (i-- > 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    }

    public synchronized void test2() {
        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 TestSynchronized myt2 = new TestSynchronized();
        Thread test1 = new Thread(new Runnable() {
            public void run() {
                myt2.test1();
            }
        }, "test1");
        Thread test2 = new Thread(new Runnable() {
            public void run() {
                myt2.test2();
            }
        }, "test2");
        test1.start();
        test2.start();
    }
}

test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0
test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0

上述的程式碼,第一個方法時用了同步程式碼塊的方式進行同步,傳入的物件例項是this,表明是當前物件,當然,如果需要同步其他物件例項,也可傳入其他物件的例項;第二個方法是修飾方法的方式進行同步。因為第一個同步程式碼塊傳入的this,所以兩個同步程式碼所需要獲得的物件鎖都是同一個物件鎖 ; main方法時分別開啟兩個執行緒,分別呼叫test1和test2方法,那麼兩個執行緒都需要獲得該物件鎖,另一個執行緒必須等待。上面也給出了執行的結果可以看到:直到test2執行緒執行完畢,釋放掉鎖,test1執行緒才開始執行。

如果我們把test2方法的synchronized關鍵字去掉,執行結果會如何呢?
test1 : 4
test2 : 4
test2 : 3
test1 : 3
test1 : 2
test2 : 2
test2 : 1
test1 : 1
test2 : 0
test1 : 0
上面是執行結果,我們可以看到,結果輸出是交替著進行輸出的,這是因為,某個執行緒得到了物件鎖,但是另一個執行緒還是可以訪問沒有進行同步的方法或者程式碼。進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個執行緒進入了同步方法,得到了物件鎖,其他執行緒還是可以訪問那些沒有同步的方法(普通方法)


(二)類鎖的修飾(靜態)方法和程式碼塊

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

    public static synchronized void test2() {
        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 TestSynchronized myt2 = new TestSynchronized();
        Thread test1 = new Thread(new Runnable() {
            public void run() {
                myt2.test1();
            }
        }, "test1");
        Thread test2 = new Thread(new Runnable() {
            public void run() {
                TestSynchronized.test2();
            }
        }, "test2");
        test1.start();
        test2.start();
    }
}

test1 :4
test2 :4
test1 :3
test2 :3
test2 :2
test1 :2
test2 :1
test1 :1
test1 :0
test2 :0

 

上面程式碼synchronized同時修飾靜態方法和例項方法,但是執行結果是交替進行的,這證明了類鎖和物件鎖是兩個不一樣的鎖,控制著不同的區域,它們是互不干擾的。同樣,執行緒獲得物件鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。

三、對比

public class ThreadTest {
    public static final String Lock = "lock";
    public final Object obj = new Object();
    synchronized void test1() throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("test1=" + i);
        }
    }

    synchronized static void test2() throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("test2=" + i);
        }
    }

    void test3() throws InterruptedException {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("test3=" + i);
            }
        }
    }

    void test4() throws InterruptedException {
        synchronized (this.getClass()) {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("test4=" + i);
            }
        }
    }

    void test5() throws InterruptedException {
        synchronized (Lock) {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("test5=" + i);
            }
        }
    }

    void test6() throws InterruptedException {
        synchronized (obj) {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("test6=" + i);
            }
        }
    }

    void test7() throws InterruptedException {
        synchronized (ThreadTest.Lock) {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("test7=" + i);
            }
        }
    }
}

一、同一個物件的同一個同步方法是同步的

二、同一個物件的synchronized方法和synchronized(this)塊是同步的
方法test1和test3同步
這兩個方法的鎖都是加在當前物件上

三、類的static方法和synchronized(該類)塊是同步的
方法test2和test4同步
這兩個方法的鎖都是加在ThreadTest類上面,方法test4的synchronized (this.getClass())塊換成synchronized (ThreadTest.class),結果是一樣的,但是如果換成比如synchronized (String.class)就不能同步了
因為鎖是加在類上面,所以不僅相同類的這兩種方法是同步的,同一個類的不同物件之間的這兩種方法也是同步的

四、第二點的兩種方法和第三點的兩種方法不同步
test1和test2不同步,test1和test4不同步
test3和test2不同步,test3和test4不同步

五、synchronized(變數)跟前面四種都不同步
因為鎖加的目標不一樣,既不是當前類也不是當前物件。
synchronized (obj)和synchronized (this)的用法基本一致,對當前物件加鎖。
而synchronized (Lock)除了當前物件以外,本類的其他新建物件也會同步加鎖,甚至於其他類如果呼叫的synchronized (ThreadTest.Lock)也會同步。

六、ThreadTest2的test7方法跟ThreadTest的test5方法是同步的;