關於多線程之synchronized關鍵字

對象及變量的併發訪問

1.synchronized同步方法

synchronized關鍵字可用來保障原子性、可見性、和有序性。

我們需要掌握的是:

1)synchronized對象監視器爲Object時的使用方法

2)synchronized對象監視器爲Class的使用方法

1.1方法內的變量爲線程安全

非線程安全問題存在於實例變量中,對於方法內部的私有變量,則不存在線程安全問題。

1.2 實例變量非線程安全問題

如果多個線程共同訪問一個對象中的實例變量,則有可能會出現非線程安全問題。

兩個線程同時訪問同一個對象中的同步方法時一定是線程安全的。

1.3 多個對象多個鎖

創建類HasSelfPrivateNum:

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void add(String username){
        try {
            if(username.equals("a")){
                num =100;
                System.out.println("a set over");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(username+"num="+num);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

創建兩個線程類:

public class ThreadA extends  Thread {
    private HasSelfPrivateNum numRef;
    public  ThreadA(HasSelfPrivateNum numRef){
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.add("a");
    }
}
ThreadB:
public class ThreadB extends  Thread {
    private HasSelfPrivateNum numRef;
    public ThreadB(HasSelfPrivateNum numRef){
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.add("b");
    }
}

最後的運行結果爲:

a set over
b set over
bnum=200
anum=100

總結:本示例創建了兩個業務對象(numRef1,numRef2),在系統中產生了兩個鎖,線程和業務對象屬於一對一的關係,每個線程執行自己所屬業務對象中的同步方法,不存在爭搶關係,所以運行結果是異步的,更具體的講,在上面的這個示例中創建了兩個業務對象,所以產生兩份實例變量,每個線程訪問自己的實例變量,所以加不加synchronized關鍵字都是線程安全的。

如果運行結果爲a set over,anum =100,b set over,bnum = 200,才說明線程是同步進行的。

1.4 關於synchronized同步鎖方法的結論

1.4.1 A線程持有Object對象的lock鎖,B線程可以以異步的方式調用object對象中的非synchronized類型的方法
1.4.2 A線程持有object對象的lock鎖,B線程如果在這時調用object對象中的synchronized類型的方法,則需要等待。
1.4.3 在方法聲明處添加synchronized並不是鎖方法,而是鎖當前類的對象
1.4.4 在Java中只有“將對象作爲鎖”這種說法,並沒有“鎖方法”這種說法
1.4.5 在Java中,“鎖”就是對象,“對象”可以映射成“鎖”,哪個線程拿到這把鎖,哪個線程就可以執行這個對象中的synchronized同步方法
1.4.6 如果在X對象中使用了synchronize的關鍵字聲明非靜態方法,則X對象就被當成鎖。

1.5 髒讀

通過synchronized關鍵字可以解決髒讀的問題。

拿一個對象A的setValue()和getValue()爲例,其中setValue()被synchronized修飾,而getValue()沒有。

現在有兩個線程A和線程B

A先執行,setValue(),A線程未執行完,B線程無法調用setValue(),但是getValue()可被B線程調用,此時出現髒讀

解決方案:給getValue()也加上synchronized修飾。

1.6 爲了解決synchronized同步方法的等待時間過長的方案------->synchronized同步代碼塊synchronized(this)

synchronized同步代碼塊的作用:(synchronized同步代碼塊也是一樣)

1.對其他synchronized同步方法或synchronized(this)同步代碼塊調用呈同步效果

2.同一時間只有一個線程可以執行synchronized同步方法中代碼

1.7 synchronized(非this對象)

1.7.1 除了使用synchronized(this)格式來創建同步代碼塊,其實Java還支持將“任意對象”作爲鎖來實現同步的功能,這個“任意對象”大多數是實例變量及方法的參數,使用格式爲
1.7.2 synchronized(非this對象x)同步代碼塊的作用:當多個線程爭搶相同的“非this對象x”的鎖時,同一時間只有一個線程可以執行synchronized(非this 對象x)同步代碼塊中的代碼s
1.7.3 鎖非this 對象具有一定的優點:如果一個類中有很多個synchronized方法,則這時雖然能實現同步,但影響運行效率,如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,因爲有兩把鎖,不與其他鎖this同步方法爭搶this 鎖,可大大提高運行效率。

1.8 多個鎖就是異步執行

使用synchronized(非this對象x)同步代碼“代碼塊”操作時,鎖必須是同一個,否則就是異步調用,交叉執行。

下面使用個例子證明多個鎖時是異步執行的結果:

public class ThreadA extends  Thread{
    private  Service service;
    public ThreadA(Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.a();
    }
}

ThreadB:
public class ThreadB extends  Thread{
    private  Service service;
    public ThreadB(Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.b();
    }
}

創建個模擬業務類:Service

public class Service {
    private String anyString  = new String();
    public void a(){
        try {
            synchronized (anyString){
                System.out.println("a begain");
            }
            Thread.sleep(3000);
            System.out.println("a end!");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void b(){
        System.out.println("b begain");
        System.out.println("b end!");
    }
}

最後運行我們的測試類:

public class RunTest {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }
}

最後的運行結果我們看到當A線程還沒跑完的時候,B線程也已經在開始了,由於鎖不同,所以運行結果是異步調用的;

a begain
b begain
b end!
a end!

和synchronized(非this對象X)和synchronized(this對象或者方法)不同的是,後者多個線程時,得等先拿到鎖的那個線程跑完了才能執行其他synchronized的方法而synchronized(非this對象x)可以和其他synchronized方法異步執行。

1.9 細化synchronize(非this對象x)的三個結論:synchronize的(非this對象x)是將x對象本身作爲“對象監視器”

1.9.1 當多個線程同時執行synchronized(x){}同步代碼塊時呈現同步效果
1.9.2 當其他線程執行x對象中synchronized同步方法時呈現同步效果
1.9.3 當其他線程執行x對象方法裏面的synchronized(this)代碼塊時呈現同步效果

需要注意的是,如果其他線程調用不加synchronized關鍵字的方法,則還是異步調用,而且鎖只是類繼承的環境。

1.20 靜態同步synchronized方法與synchronized(class) 代碼塊

關鍵字synchronized應用在static靜態方法上時,那是對當前的*.java問對應的Class類對象進行持鎖,Class類的對象是單例的。更具體的說,在靜態static方法上使用synchronized關鍵字聲明同步方法時,使用當前靜態方法所在類對應Class類的單例對象作爲鎖。

而將synchronized加在非static方法上是將方法所在類的對象作爲鎖。

下面驗證兩個不是同一個鎖的。

Service.java:

public class Service {
    synchronized public static void printA(){
        try {
            System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
                    "在"+System.currentTimeMillis()+"進入了printA");
            Thread.sleep(3000);
            System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
                    "在"+System.currentTimeMillis()+"離開了printA");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    synchronized public static void printB(){
        System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"進入了printB");
        System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"離開了printB");
    }

    synchronized public void printC(){
        System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"進入了printC");
        System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"離開了printC");
    }

}

創建三個線程:

public class ThreadA extends  Thread{
    private  Service service;
    public ThreadA(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printA();
    }
}
public class ThreadB extends  Thread{
    private  Service service;
    public ThreadB(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printB();
    }
}
public class ThreadC extends  Thread{
    private  Service service;
    public ThreadC(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printC();
    }
}

最後運行測試類:

public class RunTest {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
        ThreadC c = new ThreadC(service);
        c.setName("c");
        c.start();
    }
}

最後的運行結果如圖:方法C是異步執行,同步synchronized放在static方法上可以對類的所有對象的實例起作用,比如此時的線程A和B,不同的service對象,但是對printA和printB依然是同步執行

線程的名稱爲:a在1592551267059進入了printA
線程的名稱爲:c在1592551267062進入了printC
線程的名稱爲:c在1592551267062離開了printC
線程的名稱爲:a在1592551270059離開了printA
線程的名稱爲:b在1592551270059進入了printB
線程的名稱爲:b在1592551270060離開了printB

1.21 同步synchronized(A.class)和synchronized A中的static方法是同樣作用,可以對類的所有對象的實例起作用,但是非static的synchronized只能對同一個實例有作用

1.22 String常量池特性與同步相關的問題與解決方案

問題:當synchronized(String對象)時,假設兩個線程的值都是“AA”,String的特性會判斷“AA"=="AA"爲true,則此時兩個線程的鎖相同,B線程將不會被執行。

解決:所以大多數情況下,不適用String作爲鎖對象,而改用其他,而用new Object()實例化一個新的Object對象,它並不放入緩存池中,或者執行new String ()創建不同的字符串對象,形成不同的鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章