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


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

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

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

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

如果此對象的對象鎖已被其他調用者占用,則需要等待此鎖被釋放

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

同步靜態方法/靜態變量互斥體
由於一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只由一份。所以,一旦一個靜態的方法被申明為synchronized。此類所有的實例化對象在調用此方法,共用同一把鎖,我們稱之為類鎖。一旦一個靜態變量被作為synchronized block的mutex。進入此同步區域時,都要先獲得此靜態變量的對象鎖

 

代碼

/**
 * 同步代碼塊與同步實例方法的互斥
 * 
 * @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 : 0

上述的代碼,第一個方法時用了同步代碼塊的方式進行同步,傳入的對象實例是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的缺陷:當某個線程進入同步方法獲得對象鎖,那么其他線程訪問這里對象的同步方法時,

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

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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM