從此不怕Synchronized鎖

  • Synchronized的使用

? Synchronized是通過監視器保證線程同步從而保證線程安全。但是Synchronized鎖可以鎖對象和鎖類,并會產生不同的效果,通過下面的案例徹底理解Synchronized鎖的使用方式。

即:

? 對于普通的同步方法,鎖是當前實例對象

? 對于靜態同步方法,鎖是該類

? 對于同步方法塊,鎖是Synchronized括號里面配置的對象。

下面通過代碼具體分析幾種情況。要想了解并發情況,首先我們必須知道,類信息、實例對象分別存放在什么位置。類的信息,包括靜態變量都是存放在方法區中;而實例對象,包括類的成員變量,是存放在堆中。

1. 成員變量+普通同步方法+鎖

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    public synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join();		//等待線程執行完
        thread2.join();		//等待線程執行完
        System.out.println(sync01.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
	2000
        0
public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    public synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join();		//等待線程執行完
        thread2.join();		//等待線程執行完
        System.out.println(sync01.sum);
        System.out.println(sync02.sum);
    }
}

result:
	1000
        1000
        0

分析:

? 理解了這兩個demo再去理解同步代碼塊下的多線程安全問題,將會達到事半功倍的效果。上面兩個demo主要是想表達,成員變量和類的實例化對象一樣,都是在堆中創建,每次new對象,都會在堆中產生一個新的對象。所以第一個demo中,當在線程同步的情況下,兩個線程去操作同一個對象,最后的結果是2000;而第二個demo中,兩個線程去操作兩個實例化對象,所以每個對象的成員變量sum為1000。因此我們也可以發現,其實在第一個demo中才會有線程安全問題,在第二個demo中是不存在線程安全問題的,有疑問可以去掉鎖驗證一下。通過這個例子也可以去理解為什么sping中多例是線程安全的。

2. 成員變量+同步代碼塊+對象鎖

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sync01.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
	2000
        0
public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sync01.sum);
        System.out.println(sync02.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
	1000
	1000
	0

分析:

? 同案例1一樣,Demo1為兩個線程執行一個實例化對象,但是加了Synchronized對象鎖,因此實現了同步,保證線程安全。Demo2為兩個線程執行兩個實例化對象,各自利用各自的成員變量sum,因此不會產生并發安全問題。

3. 成員變量+同步代碼塊+類鎖

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sync01.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
	2000
	0
public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {

        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sync01.sum);
        System.out.println(sync02.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
	1000
	1000
	0

分析:

? Demo1為兩個線程執行一個實例化對象,會產生并發安全問題,但是加了同步類鎖(可以理解為鎖的級別比對象鎖更高),當然也可以實現并發同步,保證線程安全。而Demo2同樣實例化兩個對象,各自操作各自的成員變量sum,也不會產生線程安全問題。

4. 靜態變量+普通方法+鎖

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private synchronized void add() {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

result:
	2000
public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private synchronized void add() {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

輸出結果不確定(存在線程安全問題)

分析:

? 從案例4我們要注意,由成員變量換成靜態變量,而上面已經講過,靜態變量存放在方法區中,所有實例化對象共享一份。再看Demo1,兩個線程執行同一個實例化對象,然后添加的是對象鎖,因此該對象鎖能鎖住該實例化對象,實現同步,保證線程安全。

? Demo2是兩個線程執行兩個實例化對象,添加的是對象鎖,相當于各自的對象鎖鎖住各自的對象,而靜態變量是類變量,存放在方法區中而不是堆中,此情況對象鎖并不能保證線程同步,因此會產生線程安全問題。

5. 靜態變量+靜態方法+鎖

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    static synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

result:
	2000
public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    static synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

result:
	2000

分析:

? 該案例相比案例4,鎖由對象鎖換成類鎖,對于Demo1,兩個線程操作一個對象,毫無疑問會使其同步。而Demo2,兩個線程執行兩個實例化對象,由于使用的是類鎖,也會使線程同步,保證線程安全。

6. 靜態變量+同步代碼塊+對象鎖

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

result:
	2000
public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

輸出結果不確定(存在線程安全問題)

分析:該案例和案例4一樣,添加對象鎖,只能保證同一對象的并發同步,不能保證不同對象同步。

7. 靜態變量+同步代碼塊+類鎖

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

result:
	2000
public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join();  //等待線程執行完
        thread2.join();  //等待線程執行完
        System.out.println(sum);
    }
}

result:
	2000

分析:

? 該案例同案例5一樣,添加類鎖,無論是多個線程操作一個實例化對象還是多個實例化對象,都能保證線程安全。

總結:

? 對象鎖只能保證各自實例化對象并發的線程安全問題。類鎖可以保證多個實例化多謝的安全問題

posted @ 2020-07-06 22:27  何故愁為河邊柳  閱讀(...)  評論(...編輯  收藏
色网站直播