1. 程式人生 > >12、synchronized同步方法

12、synchronized同步方法

ps:最近幾天帶媳婦回了趟家,博文沒有更新。

 

目錄

方法內部的私有變數為執行緒安全

例項變數非執行緒安全

給方法加鎖

synchronized取得的鎖是物件鎖

A執行緒持有同步方法的鎖,B執行緒可以非同步的呼叫該物件中的非同步方法

髒讀


方法內部的私有變數為執行緒安全

在使用synchronized同步方法之前,我們先來做多個執行緒訪問方法內部的私有變數,是否存線上程安全的問題。

package com.demo12;

public class MyObject {
    public void addNum(String name){
        try {
            int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 賦值完畢");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 賦值完畢");
            }
            System.out.println("執行緒name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.demo12;

public class ThreadA extends Thread {
    private MyObject myObject;

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("a");
    }
}
package com.demo12;

public class ThreadB extends Thread{
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("b");
    }
}
package com.demo12;

public class Run {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        ThreadA threadA = new ThreadA(myObject);
        ThreadB threadB = new ThreadB(myObject);
        threadA.start();
        threadB.start();
    }
}

執行結果:

a 賦值完畢
執行緒name=a;num=100
b 賦值完畢
執行緒name=b;num=200

由輸出結果可以看出:a執行緒賦值完畢之後,b執行緒才對num進行賦值。哪怕a sleep()了,b執行緒也沒搶佔cpu資源去執行賦值。方法內部的私有變數,不存線上程安全的問題。

例項變數非執行緒安全

package com.demo12;

public class MyObject {
    private int num = 0;
    public void addNum(String name){
        try {
            //int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 賦值完畢");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 賦值完畢");
            }
            System.out.println("執行緒name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

將上例中的MyObject中的方法內部的變數改為例項變數,執行結果為:

a 賦值完畢
b 賦值完畢
執行緒name=b;num=200
(此處等待兩秒)
執行緒name=a;num=200

執行緒a賦值完畢之後被sleep(),這時候b拿到了Cpu資源,對num進行了賦值,最後a才拿到cpu資源,繼續走列印語句。但是num已經是被b給修改過了的。這就是非執行緒安全的場景。

給方法加鎖

這個時候,我們可以使用synchronized對方法進行加鎖,使上面的場景變成執行緒安全的!

package com.demo12;

public class MyObject {
    private int num = 0;
    synchronized public void addNum(String name){
        try {
            //int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 賦值完畢");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 賦值完畢");
            }
            System.out.println("執行緒name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行結果:

a 賦值完畢
(此處等待了2秒)
執行緒name=a;num=100
b 賦值完畢
執行緒name=b;num=200

很明顯為執行緒安全的了。

synchronized取得的鎖是物件鎖

修改上述程式碼如下:

package com.demo12;

public class MyObject {
    private int num = 0;
    synchronized public void addNum(String name){
        try {
            //int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 賦值完畢");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 賦值完畢");
            }
            System.out.println("執行緒name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.demo12;

public class ThreadA extends Thread {
    private MyObject myObject;

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("a");
    }
}
package com.demo12;

public class ThreadB extends Thread{
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("b");
    }
}
package com.demo12;

public class Run {
    public static void main(String[] args) {
        MyObject myObject1= new MyObject();
        MyObject myObject2= new MyObject();
        ThreadA threadA = new ThreadA(myObject1);
        threadA.start();
        ThreadB threadB = new ThreadB(myObject2);
        threadB.start();
    }
}

執行結果為:

a 賦值完畢
b 賦值完畢
執行緒name=b;num=200
(此處等待了2秒)
執行緒name=a;num=100

分析:雖然結果打印出來的形如非同步,但是結果卻沒有亂。a , b 執行緒在獲取CPU資源之後,分別持有synchronied方法所屬的物件鎖。如果不是分別拿到的兩個物件的鎖,最後的列印結果num=200。出現非執行緒安全的問題。

A執行緒持有同步方法的鎖,B執行緒可以非同步的呼叫該物件中的非同步方法

package com.demo13;

public class MyObject {
    synchronized public void methodA(){
        try {
            System.out.println("同步methodA beigin , 執行緒名稱=" + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("同步methodA end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void methodB(){
        System.out.println("非同步methodB begin , 執行緒名稱=" + Thread.currentThread().getName());
        System.out.println("非同步methodB end");
    }
}
package com.demo13;

public class ThreadA extends Thread {
    private MyObject myObject;

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.methodA();
    }
}
package com.demo13;

public class ThreadB extends Thread {
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run(){
        myObject.methodB();
    }
}
package com.demo13;

public class Run {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        ThreadA threadA = new ThreadA(myObject);
        ThreadB threadB = new ThreadB(myObject);
        threadA.setName("A");
        threadB.setName("B");
        threadA.start();
        threadB.start();
    }
}

執行結果:

同步methodA beigin , 執行緒名稱=A
非同步methodB begin , 執行緒名稱=B
非同步methodB end
(此處等待5秒)
同步methodA end

髒讀

上面所述的情況可能導致髒讀

package com.demo14;

public class MyObject  {
    private String username = "1";
    private String password = "11";
    synchronized public void setValue(String username , String password){
        try {
            System.out.println("賦值執行緒名=" + Thread.currentThread().getName());
            this.username = username;
            Thread.sleep(2000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void getValue(){
        System.out.println("取值執行緒名=" + Thread.currentThread().getName());
        System.out.println("username=" + username );
        System.out.println("password=" + password);
    }
}
package com.demo14;

public class ThreadA extends Thread {
    MyObject myObject = new MyObject();

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.setValue("2","22");
    }
}
package com.demo14;

public class ThreadB extends Thread {
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.getValue();
    }
}
package com.demo14;

public class Run {
    public static void main(String[] args) {
        try {
            MyObject myObject = new MyObject();
            ThreadA threadA = new ThreadA(myObject);
            ThreadB threadB = new ThreadB(myObject);
            threadA.setName("A");
            threadB.setName("B");
            threadA.start();
            Thread.sleep(500); //給時間 保證讓A將username賦值完畢
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行結果:

賦值執行緒名=A
取值執行緒名=B
username=2
password=11

很明顯出現了髒讀。修改MyObject.java檔案

package com.demo14;

public class MyObject  {
    private String username = "1";
    private String password = "11";
    synchronized public void setValue(String username , String password){
        try {
            System.out.println("賦值執行緒名=" + Thread.currentThread().getName());
            this.username = username;
            Thread.sleep(2000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void getValue(){
        System.out.println("取值執行緒名=" + Thread.currentThread().getName());
        System.out.println("username=" + username );
        System.out.println("password=" + password);
    }
}

執行結果為:

賦值執行緒名=A
取值執行緒名=B
username=2
password=22

上述說明:B執行緒想要呼叫物件中沒有被A執行緒鎖住的同步方法B,必須等A執行緒釋放A方法的同步鎖。