Java多線程學習(三)——synchronized(上)

在前兩節的《Java多線程學習(一)——多線程基礎》《Java多線程學習(二)——Thread類的方法介紹》中咱們接觸了線程安全和非線程安全的概念,這節就來學習一下synchronized關鍵字的使用。java

一、變量的線程安全性

「非線程安全」問題存在於「實例變量中」,若是是方法內部私有的變量,則不存在「非線程安全」的問題。若是兩個線程同時操做對象中的實例變量,會出現非線程安全的問題,解決方法是在方法上添加添加synchronized關鍵字控制同步。git

二、多個對象多個鎖

先看代碼:github

public class HasSelfPrivateNum {
    // 建立被同步關鍵字修飾的方法
    private int num = 0;
    synchronized public void add(String name){

        try {
            if ("a".equals(name)){
                num = 100;
                System.out.println("a set over");
                Thread.sleep(2000);
            }else {
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(name + " num = " + num);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}
複製代碼
public class ThreadA extends Thread {
// 建立線程,構造器傳參爲上面方法的對象
    private HasSelfPrivateNum hasSelfPrivateNum;

    public ThreadA(String name, HasSelfPrivateNum hasSelfPrivateNum){
        super();
        this.hasSelfPrivateNum = hasSelfPrivateNum;
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        hasSelfPrivateNum.add(this.getName());
    }
}
複製代碼
// main方法和輸出
 public static void main(String[] args){
        HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
        HasSelfPrivateNum hasSelfPrivateNum1 = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA("a", hasSelfPrivateNum);
        ThreadA threadA1 = new ThreadA("b", hasSelfPrivateNum1);
        threadA.start();
        threadA1.start();
    }

b set over
a set over
b num = 200
a num = 100
複製代碼

因爲本實例建立了兩個HasSelfPrivateNum對象,因此就會產生兩個鎖,因此運行的結果是異步的。安全

synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖。因此在上面的實例中,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法所屬對象的鎖Lock,那麼其餘線程只能呈等待狀態,前提是多個線程訪問的是同一個對象。可是若是多個線程訪問多個對象,JVM會建立多個鎖。微信

將main方向修改一下:多線程

public static void main(String[] args){
        HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
        //HasSelfPrivateNum hasSelfPrivateNum1 = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA("a", hasSelfPrivateNum);
        ThreadA threadA1 = new ThreadA("b", hasSelfPrivateNum);
        threadA.start();
        threadA1.start();
    }
----
a set over
a num = 100
b set over
b num = 200
複製代碼

能夠看到threadA和threadA1拿到的是一個對象的鎖,因此是順序輸出的。異步

三、synchronized方法與所對象

經過上面咱們知道synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖。若是多個線程訪問的是同一個對象,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法,那麼其餘線程只能呈等待狀態。若是多個線程訪問的是多個對象則不必定,由於多個對象會產生多個鎖。ide

若是多個線程訪問的是同一個對象中的未被synchronized關鍵字修飾的方法,線程會異步調用未被修飾的方法。學習

四、髒讀

在賦值的時候進行了同步,但在取值的時候可能會出現一些意想不到的意外,這種狀況就是髒讀。發生髒讀的狀況是在讀取實例變量的時候,此值已經被其餘線程修改。測試

public class DirtyReadTest {
    // 建立同步修改數據方法,非同步取數據方法
    public String username = "A";
    public String password = "AA";
    synchronized public void setValue(String username, String password){
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("setValue method: username = " + username + " password = " + password);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public void getValue(){
        System.out.println("getValue method: username = " + username + " password = " + password);
    }

}
複製代碼
public class ThreadTest extends Thread {
    private DirtyReadTest dirtyReadTest;
    public ThreadTest(DirtyReadTest dirtyReadTest){
        super();
        this.dirtyReadTest=dirtyReadTest;
    }

    @Override
    public void run() {
        super.run();
        dirtyReadTest.setValue("B", "BB");
    }
}
複製代碼

輸出:

public static void main(String[] args) throws Exception{
        DirtyReadTest dirtyReadTest = new DirtyReadTest();
        ThreadTest threadTest = new ThreadTest(dirtyReadTest);
        threadTest.start();
        Thread.sleep(200);
        dirtyReadTest.getValue();
    }
getValue method: username = B password = AA
setValue method: username = B password = BB
複製代碼

解決方法是在getValue方法上加上synchronized關鍵字。

當線程1調用一個對象的synchronized方法A時候,線程1就會獲取該對象的鎖,這是線程2是沒法獲取該對象的被synchronized修飾的任何方法,可是能夠訪問未被synchronized修飾的方法。

五、synchronized鎖重入

在使用synchronized時,當一個線程獲得一個對象的鎖後,沒有釋放,再次請求該對象的鎖是能夠再次獲取到的。一個對象中的synchronized修飾的方法能夠訪問該對象中其餘被synchronized修飾的方法,若是不可重入鎖會形成死鎖。

測試方法:

public class Service {
    synchronized public void service1(){
        System.out.println("service1");
        service2();
    }

    synchronized public void service2(){
        System.out.println("service2");
        service3();
    }

    synchronized public void service3(){
        System.out.println("service3");

    }
}
複製代碼
public class ThreadTest extends Thread {
    @Override
    public void run() {
        super.run();
        Service service = new Service();
        service.service1();
    }
}
複製代碼
public static void main(String[] args){
        ThreadTest threadTest = new ThreadTest();
        threadTest.start();
    }

service1
service2
service3
複製代碼

可重入鎖也支持在父子類繼承的環境中,子類能夠經過可重入鎖調用父類的同步方法。這裏再也不寫代碼了,能夠自行寫一寫。

六、出現異常,所自動釋放

當一個線程執行的代碼出現異常時,其全部的鎖會自動釋放。

七、同步不具備繼承性

若是父類有一個帶synchronized關鍵字的方法,子類繼承並重寫了這個方法。 可是同步不能繼承,因此仍是須要在子類方法中添加synchronized關鍵字。

本節代碼的GitHub


歡迎關注公衆號:

公衆號微信
相關文章
相關標籤/搜索