Java多線程學習(二)synchronized關鍵字(1)

轉載請備註地址: https://blog.csdn.net/qq_3433...java

Java多線程學習(二)將分爲兩篇文章介紹synchronized同步方法另外一篇介紹synchronized同步語句塊
系列文章傳送門:程序員

Java多線程學習(一)Java多線程入門面試

Java多線程學習(二)synchronized關鍵字(1)編程

java多線程學習(二)synchronized關鍵字(2) 安全

Java多線程學習(三)volatile關鍵字微信

Java多線程學習(四)等待/通知(wait/notify)機制多線程

Java多線程學習(五)線程間通訊知識點補充併發

Java多線程學習(六)Lock鎖的使用異步

Java多線程學習(七)併發編程中一些問題ide

系列文章將被優先更新與微信公衆號<font color="red">「Java面試通關手冊」</font>,歡迎廣大Java程序員和愛好技術的人員關注。

(1) synchronized同步方法

本節思惟導圖:

思惟導圖源文件+思惟導圖軟件關注微信公衆號:<font color="red">「Java面試通關手冊」</font>回覆關鍵字:<font color="red">「Java多線程」</font>免費領取。

一 簡介

Java併發編程這個領域中<font color="red">synchronized關鍵字</font>一直都是元老級的角色,好久以前不少人都會稱它爲<font color="red">「重量級鎖」</font>。可是,在JavaSE 1.6以後進行了主要包括爲了<font color="red">減小得到鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖</font>以及其它各類優化以後變得在某些狀況下並非那麼重了。

這一篇文章不會介紹synchronized關鍵字的實現原理,更多的是synchronized關鍵字的使用。若是想要了解的能夠看看方騰飛的《Java併發編程的藝術》。

二 變量安全性

<font color="red">「非線程安全」</font>問題存在於<font color="red">「實例變量」</font>中,若是是<font color="red">方法內部的私有變量</font>,則不存在<font color="red">「非線程安全」</font>問題,所得結果也就是<font color="red">「線程安全」</font>的了。

若是兩個線程同時操做對象中的實例變量,則會出現<font color="red">「非線程安全」</font>,解決辦法就是在方法前加上<font color="red">synchronized關鍵字</font>便可。前面一篇文章咱們已經講過,並且貼過相應代碼,因此這裏就再也不貼代碼了。

三 多個對象多個鎖

先看例子:

<font size="2">HasSelfPrivateNum.java</font>

public class HasSelfPrivateNum {

    private int num = 0;

    synchronized public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                //若是去掉hread.sleep(2000),那麼運行結果就會顯示爲同步的效果
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

<font size="2">ThreadA.java</font>

public class ThreadA extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }

}

<font size="2">ThreadB.java</font>

public class ThreadB extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }

}

<font size="2">Run.java</font>

public class Run {

    public static void main(String[] args) {

        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

        ThreadA athread = new ThreadA(numRef1);
        athread.start();

        ThreadB bthread = new ThreadB(numRef2);
        bthread.start();

    }

}

<font size="2">運行結果:</font>
a num=100停頓一會才執行
多個對象多個鎖
上面實例中兩個線程ThreadA和ThreadB分別訪問同一個類的不一樣實例的相同名稱的同步方法,可是效果確實異步執行。

<font color="red">爲何會這樣呢?</font>

這是由於<font color="red">synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖</font>。因此在上面的實例中,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法<font color="red">所屬對象的鎖Lock</font>,那麼其餘線程只能呈等待狀態,<font color="red">前提是多個線程訪問的是同一個對象</font>。本例中很顯然是兩個對象。

在本例中建立了兩個HasSelfPrivateNum類對象,因此就<font color="red">產生了兩個鎖</font>。當ThreadA的引用執行到addI方法中的runThread.sleep(2000)語句時,ThreadB就會<font color="red">「伺機執行」</font>。因此纔會致使執行結果如上圖所示(備註:因爲runThread.sleep(2000),「a num=100」停頓了兩秒才輸出)

四 synchronized方法與鎖對象

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

<font color="red">那麼咱們思考一下當多個線程訪問的是同一個對象中的非synchronized類型方法會是什麼效果?</font>

答案是:會異步調用非synchronized類型方法,解決辦法也很簡單在非synchronized類型方法前加上synchronized關鍵字便可。

五 髒讀

發生髒讀的狀況實在讀取實例變量時,此值已經被其餘線程更改過。

<font size="2">PublicVar.java</font>

public class PublicVar {

    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 thread name="
                    + Thread.currentThread().getName() + " username="
                    + username + " password=" + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     //該方法前加上synchronized關鍵字就同步了
     public void getValue() {
        System.out.println("getValue method thread name="
                + Thread.currentThread().getName() + " username=" + username
                + " password=" + password);
    }
}

<font size="2">ThreadA.java</font>

public class ThreadA extends Thread {

    private PublicVar publicVar;

    public ThreadA(PublicVar publicVar) {
        super();
        this.publicVar = publicVar;
    }

    @Override
    public void run() {
        super.run();
        publicVar.setValue("B", "BB");
    }
}

<font size="2">Test.java</font>

public class Test {

    public static void main(String[] args) {
        try {
            PublicVar publicVarRef = new PublicVar();
            ThreadA thread = new ThreadA(publicVarRef);
            thread.start();

            Thread.sleep(200);//打印結果受此值大小影響

            publicVarRef.getValue();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

<font size="2">運行結果:</font>
運行結果
解決辦法:getValue()方法前加上synchronized關鍵字便可。

<font size="2">加上synchronized關鍵字後的運行結果:</font>
運行結果

六 synchronized鎖重入

<font color="red">「可重入鎖」</font>概念是:<font color="red">本身能夠再次獲取本身的內部鎖</font>。好比一個線程得到了某個對象的鎖,此時這個對象鎖尚未釋放,當其再次想要獲取這個對象的鎖的時候仍是能夠獲取的,若是不可鎖重入的話,就會形成死鎖。

<font size="2">Service.java</font>

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");
    }

}

<font size="2">MyThread.java</font>

public class MyThread extends Thread {
    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }

}

<font size="2">Run.java</font>

public class Run {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

<font size="2">運行結果:</font>
運行結果
另外<font color="red">可重入鎖也支持在父子類繼承的環境中</font>

<font size="2">Main.java:</font>

public class Main {

    public int i = 10;

    synchronized public void operateIMainMethod() {
        try {
            i--;
            System.out.println("main print i=" + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

<font size="2">Sub.java:</font>

public class Sub extends Main {

    synchronized public void operateISubMethod() {
        try {
            while (i > 0) {
                i--;
                System.out.println("sub print i=" + i);
                Thread.sleep(100);
                this.operateIMainMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

<font size="2">MyThread.java:</font>

public class MyThread extends Thread {
    @Override
    public void run() {
        Sub sub = new Sub();
        sub.operateISubMethod();
    }
}

<font size="2">Run.java:</font>

public class Run {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

<font size="2">運行結果:</font>
運行結果

說明當存在父子類繼承關係時,子類是徹底能夠經過「可重入鎖」調用父類的同步方法。

另外出現異常時,其鎖持有的鎖會自動釋放。

七 同步不具備繼承性

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

參考:
《Java多線程編程核心技術》

歡迎關注個人微信公衆號:"Java面試通關手冊"(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取):
微信公衆號

相關文章
相關標籤/搜索