java多線程詳解(3)-線程的互斥與同步

前言:前一篇文章主要描述了多線程中訪成員變量與局部變量問題,咱們知道訪成員變量有線程安全問題,在多線程程序中java

咱們能夠經過使用synchronized關鍵字完成線程的同步,可以解決部分線程安全問題安全

在java中synchronized同步關鍵字可使用在靜態方法和實例方法中使用,二者的區別在於:多線程

對象鎖與類鎖
對象鎖
當一個對象中有synchronized method或synchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先得到對象鎖。併發

若是此對象的對象鎖已被其餘調用者佔用,則須要等待此鎖被釋放高併發

類鎖
由上述同步靜態方法引伸出一個概念,那就是類鎖。其實系統中並不存在什麼類鎖。當一個同步靜態方法被調用時,系統獲取的其實就是表明該類的類對象的對象鎖
在程序中獲取類鎖
能夠嘗試用如下方式獲取類鎖
synchronized (xxx.class) {...}
synchronized (Class.forName("xxx")) {...}
同時獲取2類鎖
同時獲取類鎖和對象鎖是容許的,並不會產生任何問題,但使用類鎖時必定要注意,一旦產生類鎖的嵌套獲取的話,就會產生死鎖,由於每一個class在內存中都只能生成一個Class實例對象。測試

同步靜態方法/靜態變量互斥體
因爲一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只由一份。因此,一旦一個靜態的方法被申明爲synchronized。此類全部的實例化對象在調用此方法,共用同一把鎖,咱們稱之爲類鎖。一旦一個靜態變量被做爲synchronized block的mutex。進入此同步區域時,都要先得到此靜態變量的對象鎖this

 

代碼spa

/**
 * 同步代碼塊與同步實例方法的互斥
 * 
 * @author cary
 */
public class TestSynchronized {
    /**
     * 同步代碼塊
     */
    public void testBlock() {
        synchronized (this) {
            int i = 5;
            while (i-- > 0) {
                System.out
                        .println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    }

    /**
     * 非同步普通方法
     */
    public void testNormal() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * 同步實例方法
     */
    public synchronized void testMethod() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * 主方法分別調用三個方法
     * 
     * @param args
     */
    public static void main(String[] args) {
        final TestSynchronized test = new TestSynchronized();
        Thread test1 = new Thread(new Runnable() {
            public void run() {
                test.testBlock();
            }
        }, "testBlock");
        Thread test2 = new Thread(new Runnable() {
            public void run() {
                test.testMethod();
            }
        }, "testMethod");
        test1.start();
        ;
        test2.start();
        test.testNormal();
    }
}

執行結果線程

testBlock : 4
main : 4
testBlock : 3
main : 3
testBlock : 2
main : 2
testBlock : 1
main : 1
testBlock : 0
main : 0
testMethod : 4
testMethod : 3
testMethod : 2
testMethod : 1
testMethod : 0code

上述的代碼,第一個方法時用了同步代碼塊的方式進行同步,傳入的對象實例是this,代表是當前對象,

固然,若是須要同步其餘對象實例,也不可傳入其餘對象的實例;第二個方法是修飾方法的方式進行同步。

由於第一個同步代碼塊傳入的this,因此兩個同步代碼所須要得到的對象鎖都是同一個對象鎖,

下面main方法時分別開啓兩個線程,分別調用testBlock()和testMethod()方法,那麼兩個線程都須要得到該對象鎖,

另外一個線程必須等待。上面也給出了運行的結果能夠看到:直到testBlock()線程執行完畢,釋放掉鎖testMethod線程纔開始執行

(兩個線程沒有穿插執行,證實是互斥的)

對於普通方法

結果輸出是交替着進行輸出的,這是由於,某個線程獲得了對象鎖,可是另外一個線程仍是能夠訪問沒有進行同步的方法或者代碼。

進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個線程進入了同步方法,獲得了對象鎖,

其餘線程仍是能夠訪問那些沒有同步的方法(普通方法)

 

結論:synchronized只是一個內置鎖的加鎖機制,當某個方法加上synchronized關鍵字後,就代表要得到該內置鎖才能執行,

並不能阻止其餘線程訪問不須要得到該內置鎖的方法

類鎖的修飾(靜態)方法和代碼塊:

 

/**
 * 
 * 類鎖與靜態方法鎖
 * 
 * @author cary
 */
public class TestSynchronized2 {
    /**
     * 類鎖
     */
    public void testClassLock() {
        synchronized (TestSynchronized2.class) {
            int i = 5;
            while (i-- > 0) {
                System.out
                        .println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    }

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

    /**
     * 普通方法
     */
    public void testNormal() {
        int i = 5;
        while (i-- > 0) {
            System.out.println("normal-" + Thread.currentThread().getName()
                    + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * 靜態方法
     */
    public void testStaticNormal() {
        int i = 5;
        while (i-- > 0) {
            System.out.println("static-" + Thread.currentThread().getName()
                    + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * 測試synchronized鎖的互斥效果
     * 
     * @param args
     */
    public static void main(String[] args) {
        final TestSynchronized2 test = new TestSynchronized2();
        Thread testClass = new Thread(new Runnable() {
            public void run() {
                test.testClassLock();
            }
        }, "testClassLock");
        Thread testStatic = new Thread(new Runnable() {
            public void run() {
                TestSynchronized2.testStaticLock();
            }
        }, "testStaticLock");
        /**
         * 線程1
         */
        testClass.start();
        /**
         * 線程2
         */
        testStatic.start();
        /**
         * 成員方法
         */
        test.testNormal();
        /**
         * 靜態方法
         */
        TestSynchronized2.testStaticLock();

    }
}

 

執行結果

testClassLock : 4
normal-main : 4
normal-main : 3
testClassLock : 3
normal-main : 2
testClassLock : 2
testClassLock : 1
normal-main : 1
testClassLock : 0
normal-main : 0
testStaticLock : 4
testStaticLock : 3
testStaticLock : 2
testStaticLock : 1
testStaticLock : 0
main : 4
main : 3
main : 2
main : 1
main : 0

類鎖和靜態方法鎖線程是分前後執行的,沒有相互交叉,類鎖和靜態方法鎖是互斥的

其實,類鎖修飾方法和代碼塊的效果和對象鎖是同樣的,由於類鎖只是一個抽象出來的概念,

只是爲了區別靜態方法的特色,由於靜態方法是全部對象實例共用的,

因此對應着synchronized修飾的靜態方法的鎖也是惟一的,因此抽象出來個類鎖。

結論:類鎖和靜態方法鎖是互斥的

 

 鎖靜態方法和普通方法

/**
 * 鎖普通方法和靜態方法。
 * 
 * @author cary
 */
public class TestSynchronized3 {
    /**
     * 鎖普通方法
     */
    public synchronized void testNormal() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * 鎖靜態方法
     */
    public static synchronized void testStatic() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * 普通方法和靜態方法
     * 
     * @param args
     */
    public static void main(String[] args) {
        final TestSynchronized test = new TestSynchronized();
        Thread test1 = new Thread(new Runnable() {
            public void run() {
                test.testNormal();
            }
        }, "testNormal");
        Thread test2 = new Thread(new Runnable() {
            public void run() {
                TestSynchronized3.testStatic();
            }
        }, "testStatic");
        /**
         * 啓動普通方法線程
         */
        test1.start();
        /**
         * 啓動靜態方法線程
         */
        test2.start();

    }
}

 執行結果

testNormal : 4
testStatic : 4
testNormal : 3
testStatic : 3
testNormal : 2
testStatic : 2
testStatic : 1
testNormal : 1
testNormal : 0
testStatic : 0

 

上面代碼synchronized同時修飾靜態方法和實例方法,可是運行結果是交替進行的,

這證實了類鎖和對象鎖是兩個不同的鎖,控制着不一樣的區域,它們是互不干擾的。

一樣,線程得到對象鎖的同時,也能夠得到該類鎖,即同時得到兩個鎖,這是容許的。

到這裏,對synchronized的用法已經有了必定的瞭解。這時有一個疑問,既然有了synchronized修飾方法的同步方式,

 

爲何還須要synchronized修飾同步代碼塊的方式呢?而這個問題也是synchronized的缺陷所在

synchronized的缺陷:當某個線程進入同步方法得到對象鎖,那麼其餘線程訪問這裏對象的同步方法時,

必須等待或者阻塞,這對高併發的系統是致命的,這很容易致使系統的崩潰。若是某個線程在同步方法裏面發生了死循環,

那麼它就永遠不會釋放這個對象鎖,那麼其餘線程就要永遠的等待。這是一個致命的問題。

相關文章
相關標籤/搜索