synchronized做用範圍及用法

一、多線程的同步:多線程

1.一、同步機制:

在多線程中,可能有多個線程試圖訪問一個有限的資源,必須預防這種狀況的發生。因此引入了同步機制:在線程使用一個資源時爲其加鎖,這樣其餘的線程便不能訪問那個資源了,直到解鎖後才能夠訪問。併發

1.二、共享成員變量的例子:
成員變量與局部變量:

成員變量:
ide

若是一個變量是成員變量,那麼多個線程對同一個對象的成員變量進行操做,這多個線程是共享一個成員變量的。函數

局部變量:
this

若是一個變量是局部變量,那麼多個線程對同一個對象進行操做,每一個線程都會有一個該局部變量的拷貝。他們之間的局部變量互不影響。spa

下面舉例說明:

實現了Runnable的線程類:線程

class MyThread3 implements Runnable{

    //兩個線程操做同一個對象,共享成員變量
    //int i;
    @Override
    public void run() {
        //兩個線程操做同一個對象,各自保存局部變量的拷貝
        int i = 0;
        while(i<100){
            System.out.println(i);
            i++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}在main方法中用兩個線程操做同一個對象:

public static void main(String[] args) {

    MyThread3 myThread = new MyThread3();
    //下面兩個線程對同一個對象(Runnable的實現類對象)進行操做
    Thread thread = new Thread(myThread);
    Thread thread2 = new Thread(myThread);
    //各自保存局部變量的拷貝,互不影響,輸出200個數字
    thread.start();
    thread2.start();
}

這裏若是把i變成成員變量,則輸出100個數字。code

1.三、共享資源致使的讀取錯誤

下面舉個例子,兩個線程共用一個Number對象,經過Number類的getNumber方法獲取數據,讀取數據並改寫時,發現了重複讀操做:對象

首先建立一個Number類:blog

class Number{
    private int number = 10;
    public String getNumber(int i){
        if(number > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            number -= i;
            return "取出"+i+"成功,剩餘數量:"+number;
        }
        return "取出"+i+"失敗,剩餘數量:"+number;
    }
}線程類,在線程類中的私有屬性包含了Number類的引用:

class MyThread4 extends Thread{

    //兩個線程操做同一個對象,共享成員變量
    Number number;
    public MyThread4(Number number){
        this.number = number;
    }
    @Override
    public void run() {
        System.out.println(number.getNumber(8));
    }
}在main函數中建立兩個線程類,包含了同一個Number類實例的引用:

public static void main(String[] args) {

    Number number = new Number();
    //兩個線程操做同一個對象,共享對象number的成員變量number
    MyThread4 myThread = new MyThread4(number);
    MyThread4 myThread2 = new MyThread4(number);
    myThread.start();
    myThread2.start();
}

 

這樣,當第一個線程讀取Number中的number變量時先保存下來再休眠0.1秒,而後第二個線程再讀取number變量並保存,此時兩個線程保存了一樣的數字,在修改時,也就致使修改了同一個數字兩次。

二、同步機制的實現:
2.一、使用synchronized關鍵字建立synchronized方法:

使用synchronized關鍵字,該關鍵字修飾的方法叫作同步方法。

Java中每一個對象都有一個鎖或者稱爲監視器,當訪問某個對象的synchronized方法時,表示將該對象上鎖,而不單單是爲該方法上鎖。

這樣若是一個對象的synchronized方法被某個線程執行時,其餘線程沒法訪問該對象的任何synchronized方法(可是能夠調用其餘非synchronized的方法)。直至該synchronized方法執行完。

靜態的synchronized方法調用狀況:

當調用一個對象的靜態synchronized方法時,它鎖定的並非synchronized方法所在的對象,而是synchronized方法所在對象對應的Class對象。這樣,其餘線程就不能調用該類的其餘靜態synchronized方法了,可是能夠調用非靜態的synchronized方法。

結論:執行靜態synchronized方法鎖方法所在對象,執行非靜態synchronized方法鎖方法所在對象對應的Class對象。

下面是多線程調用靜態的方法的例子,因爲鎖定了方法所在對象對應的Class對象,其餘線程沒法調用該方法所在對象其餘的靜態synchronized方法:

/**
 * 定義一個類,包含了線程類須要調用的方法
 */
class Compute1{
    //這時若是某個線程調用該方法,
    //將鎖定synchronized方法所在對象對應的class對象,
    //而不是鎖定synchronized方法所在對象
    public synchronized static void execute(){
        for(int i = 0; i<100; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("compute1:execute1 " + i++);
        }
    }
    public synchronized static void execute2(){
        for(int i = 0; i<100; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("compute1:execute2 " + i++);
        }
    }
}main方法中兩個線程分別調用同一個對象的兩個static synchronized方法:

public static void main(String[] args) {
    Compute1 com = new Compute1();
    Thread thread1 = new Thread1(com);
    Thread thread2 = new Thread2(com);
    thread1.start();
    thread2.start();
}

 

一次只能調用一個靜態方法,直到執行完成。

2.二、使用synchronized建立同步代碼塊:

經過使用synchronized同步代碼塊,鎖定一個對象,該對象做爲可執行的標誌從而達到同步的效果:

/**
 * 定義一個類,包含了線程類須要調用的方法
 */
class Compute1{
    //經過同步代碼塊鎖定object1對象進行鎖定了其餘一樣的synchronized代碼塊
    private Object object1 = new Object();
    public void execute(){
        synchronized(object1){
            for(int i = 0; i<100; i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("compute1:execute1 " + i++);
            }
        }

    }
    public synchronized void execute2(){
        synchronized(object1){
            for(int i = 0; i<100; i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("compute1:execute2 " + i++);
            }
        }
    }
}

 

若是想要使用synchronized同步代碼塊達到和使用synchronized方法一樣的效果,能夠鎖定this引用:

synchronized(this){
    …
}
2.三、synchronized方法和synchronized同步代碼塊的區別:

synchronized同步代碼塊只是鎖定了該代碼塊,代碼塊外面的代碼仍是能夠被訪問的。

synchronized方法是粗粒度的併發控制,某一個時刻只能有一個線程執行該synchronized方法。

synchronized同步代碼塊是細粒度的併發控制,只會將塊中的代碼同步,代碼塊以外的代碼能夠被其餘線程同時訪問。

相關文章
相關標籤/搜索