java 線程安全 synchronized

1、線程安全問題:

併發編程的原則:設計併發編程的目的是爲了使程序得到更高的執行效率,但毫不能出現數據一致性(數據準確)問題,若是併發程序連最基本的執行結果準確性都沒法保證,那併發編程就沒有任何意義。數據庫

爲何會出現數據不正確:編程

  若是一個資源(變量,對象,文件,數據庫)能夠同時被不少線程使用就會出現數據不一致問題,也就是咱們說的線程安全問題。這樣的資源被稱爲共享資源或臨界區。安全

  舉個例子:多線程

    一個共享變量m,如今有兩個線程同時對它進行累加操做,各執行10000次,那麼我麼期待的結果是20000,但實際上並非這樣的。看代碼:併發

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
    private static volatile int m=0;
    public static void main(String[] args) {
        Runnable run=new SynchronizedTest();
        Thread thread1=new Thread(run);
        Thread thread2=new Thread(run);
        thread1.start();
        thread2.start();
        try {
            //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m的最終結果:"+m);
    }
    public void run() {
        for(int i=0;i<10000;i++){
            m++;
        }
    }
}

不管運行多少次 m老是小於20000。爲何會出現這樣的結果呢?當線程thread1在將m++的結果寫入內存以前,線程thread2已經從內存中讀取了m的值,並在這個值(過期值)上進行++操做,最後將m=1寫入內存中(可能就覆蓋了thread1計算的m=1的值,也多是出現thread1覆蓋了thread2的值)。出現這樣的結果是必然的。
如何控制多線程操做共享數據引發的數據準確性問題呢?使用「序列化訪問臨界資源」的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱做同步互斥訪問,也就是保證咱們的共享資源每次只能被一個線程使用,一旦該資源被線程使用,其餘線程將不得擁有使用權。在Java中,提供了兩種方式來實現同步互斥訪問:synchronized和Lock。函數

2、互斥訪問之synchronized(同步方法或者同步塊)

  互斥鎖:顧名思義,就是互斥訪問目的的鎖。post

  舉個簡單的例子:若是對臨界資源加上互斥鎖,當一個線程在訪問該臨界資源時,其餘線程便只能等待。this

  在Java中,每個對象都擁有一個鎖標記(monitor),也稱爲監視器,多線程同時訪問某個對象時,只有擁有該對象鎖的線程才能訪問。spa

  在Java中,能夠使用synchronized關鍵字來標記一個須要同步的方法或者同步代碼塊,當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便得到了該對象的鎖,其餘線程暫時沒法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程纔會釋放該對象的鎖,其餘線程才能執行這個方法或者代碼塊。經過這種方式達到咱們上面提到的在同一時刻,只能有一個線程訪問臨界資源線程

  synchronized用法:

  一、同步代碼塊

synchronized(synObject) {

         
    }

用法:將synchronized做用於一個給定的對象或類的一個屬性,因此每當有線程執行這段代碼塊,該線程會先請求獲取對象synObject的鎖,若是該鎖已被其餘線程佔有,那麼新的線程只能等待,從而使得其餘線程沒法同時訪問該代碼塊。

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
    private static volatile int m=0;
    public static void main(String[] args) {
        Runnable run=new SynchronizedTest();
        Thread thread1=new Thread(run);
        Thread thread2=new Thread(run);
        thread1.start();
        thread2.start();
        try {
            //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m的最終結果:"+m);
    }
    public void run() {
        synchronized (this) {
            for(int i=0;i<10000;i++){
                m++;
            }
        }
    }
}

該代碼是使用當前對象做爲互斥鎖,下面咱們使用類的一個屬性做爲互斥鎖。

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
    private static volatile int m=0;
    private Object object=new Object(); public static void main(String[] args) {
        Runnable run=new SynchronizedTest();
        Thread thread1=new Thread(run);
        Thread thread2=new Thread(run);
        thread1.start();
        thread2.start();
        try {
            //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m的最終結果:"+m);
    }
    public void run() {
        synchronized (object) {
            for(int i=0;i<10000;i++){
                m++;
            }
        }
    }
}

一、同步方法

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
    private static int m=0;
    public static void main(String[] args) {
        Runnable run=new SynchronizedTest();
        Thread thread1=new Thread(run);
        Thread thread2=new Thread(run);
        thread1.start();
        thread2.start();
        try {
            //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m的最終結果:"+m);
    }
    public synchronized void run() {
        for(int i=0;i<10000;i++){
            m++;
        }
    }
}

這段代碼中,synchronzied做用於一個實例方法,就是說當線程在進入run()方法前,必須獲取當前對象實例鎖,本例中對象實例鎖就是run。在這裏提醒你們認真看這三段代碼中main函數的實現,在這裏咱們使用Runnable建立兩個線程,而且這兩個線程都指向同一個Runnable接口實例,這樣才能保證兩個線程在工做中,使用同一個對象鎖,從而保證線程安全。

一種錯誤的理解:

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
    private static int m=0;
    public static void main(String[] args) {
        Thread thread1=new Thread(new SynchronizedTest());
        Thread thread2=new Thread(new SynchronizedTest());
        thread1.start();
        thread2.start();
        try {
            //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m的最終結果:"+m);
    }
    public synchronized void run() {
        for(int i=0;i<10000;i++){
            m++;
        }
    }
}

這段代碼的運行結果是錯誤的,請看main函數的實現方式,使用Runnable建立兩個線程,可是兩個線程擁有各自的Runnable實例,因此當thread1線程進入同步方法時加的是本身的對象實例鎖,而thread2在進入同步方法時關注的是本身的實例鎖,兩個線程擁有不一樣的對象實例鎖,所以沒法達到互斥的要求。


略做改動:

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
    private static int m=0;
    public static void main(String[] args) {
        Thread thread1=new Thread(new SynchronizedTest());
        Thread thread2=new Thread(new SynchronizedTest());
        thread1.start();
        thread2.start();
        try {
            //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m的最終結果:"+m);
    }
    public void run() {
        for(int i=0;i<10000;i++){
            count();
        }
     }
    public static synchronized void count(){
        m++;
    }
}

  這樣處理結果就是我麼想要的了,在這裏咱們將處理業務的代碼封裝成一個靜態的同步方法,那如今訪問該同步方法須要的是當前類的鎖,而類在內存中只有一份,因此不管如何,他們使用的都是同一個鎖(class級別的鎖)。

 3、synchronize的實現原理

深刻分析Synchronized原理

相關文章
相關標籤/搜索