java基礎之執行緒 認識一下synchronize

楚棠發表於2019-03-14

把java基礎擼一邊,從簡單的開始。

執行緒部分:

存在都是有理由的。如果沒有執行緒我們的程式只能是下圖這個樣子,想象一個下假如有1千萬個請求,每個請求1秒,這得請求多長時間

java基礎之執行緒 認識一下synchronize

如果不止一個視窗處理事情,這個時候,執行緒的優點就體現出來了,這樣執行完這麼多請求就除4了

java基礎之執行緒 認識一下synchronize

有點是會體現出來,但同時也暴露出了多執行緒的不足。一個CPU只能執行一個執行緒。四核也就是可以執行4個執行緒,平時看這開100個執行緒也沒事,一下就執行完了,但是這些CPU執行的時間片段太短,執行快所以看上去也沒事,但是如果超過了一定範疇也會出問題,(這不是本章的重點),還有就是資料容易成為髒資料,如果多個執行緒去修改同一個int,那麼這個字串最終是什麼樣呢?還有各種搶佔資源,死鎖...等等。

本章重點說的是髒資料問題。其他的後續更新

例項,多執行緒下髒資料的出現:

public class Demo21 extends Thread{

    private int va = 1;
    
    @Override
    public void run() {
        for (int i = 0 ; i < 100 ; i++){
            va ++ ;
            System.out.println("Thread Name :"+Thread.currentThread().getName() + "va :" +va);
        }
    }

    public static void main(String[] age){
        Demo21 demo21 = new Demo21();
        demo21.start();
        Demo21 demo22 = new Demo21();
        demo22.start();
        Demo21 demo33 = new Demo21();
        demo33.start();
    }

}複製程式碼

三個執行緒開啟,對va進行自加1。按照理想狀態,是1 2 3 ...300

但是結果是什麼樣呢?

Thread Name :Thread-0va :2
Thread Name :Thread-2va :2
Thread Name :Thread-1va :2
Thread Name :Thread-2va :3
Thread Name :Thread-0va :3
複製程式碼

三個執行緒 同時列印2。明明va++很短,執行很快為什麼還是會出現這個情況。

java基礎之執行緒 認識一下synchronize

簡單看一下jvm的執行空間是什麼回事,這裡看兩個區域,執行緒共享區,執行緒獨佔區。常量是會放到執行緒共享區的,也就是說沒個執行緒都可以拿到這個值,而執行緒獨佔區,裡面的資料只可以被當前執行緒享用(執行緒之間的資料通訊另說)。這樣就可以瞭解到為什麼會出現這個情況。

1:資料不是可以馬上寫的,從地中中讀到資料進入CPU快取,CPU再做修改,修改完之後在給到主存中

2:A執行緒修改資料B執行緒並不知道。

執行緒一個危險就是這個髒資料,破壞了資料的一致性 。解決這些方法java中提供了很多操作

synchronize,AtomicIntege,LongAdder

這次主要介紹synchronize

public class Demo21 implements Runnable{

    private int va = 1;

    public void get()  {
        synchronized (Demo21.class){
            va++;
            System.out.println("Thread Name :"+Thread.currentThread().getName() + "va :" +va);
        }
    }

    @Override
    public void run() {
        for (int i = 0 ; i < 100 ; i++){
            get();
        }
    }

    public static void main(String[] age){
        Demo21 demo21 = new Demo21();
        Thread thread = new Thread(demo21);
        Thread thread1 = new Thread(demo21);
        Thread thread2 = new Thread(demo21);
        thread.start();
        thread1.start();
        thread2.start();
    }

}複製程式碼

列印結果

Thread Name :Thread-0va :2
Thread Name :Thread-0va :3
Thread Name :Thread-0va :4
Thread Name :Thread-0va :5
Thread Name :Thread-1va :6
Thread Name :Thread-0va :7
Thread Name :Thread-0va :8
Thread Name :Thread-0va :9
Thread Name :Thread-0va :10
複製程式碼

可以見到加了這個關鍵字就可以順序執行。

參考一下java synchronize原理總結,對synchronize做一個初步瞭解

synchronize的底層是使用作業系統的mutex lock實現

記憶體可見性:一個執行緒對共享變數值的修改,能夠及時被其他執行緒看到。

操作原子性:持有同一個鎖的兩個同步快只能序列進入

java基礎之執行緒 認識一下synchronize

synchronize保證 1,2,3,4執行緒執行synchronize修飾包括的程式程式碼塊中,是保證進入的只有一個執行緒。synchronize可以理解為一個只允許一個執行緒進入的大門,進來一個就用它持有的鎖給鎖住,防止其他執行緒進入,當程式碼塊執行完畢之後,再開啟。

java是物件導向的語言,synchronize用的鎖也是存在java物件頭裡。

synchronized有三種使用方式:

1:修飾例項方法

2:修飾靜態方法

3:修飾程式碼塊


修飾例項方法:

public class A {

    public void A(){
        System.out.println("A");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void B(){
        System.out.println("B");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}複製程式碼

public class Demo21 {

    public static void main(String[] age){
        A a = new A();
        new Thread(new Runnable() {
            @Override
            public void run() {
                long l = System.currentTimeMillis();
                a.A();
                long l2 = System.currentTimeMillis() - l;
                System.out.println(l2);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                long l = System.currentTimeMillis();
                a.B();
                long l2 = System.currentTimeMillis() - l;
                System.out.println(l2);
            }
        }).start();
    }

}複製程式碼

如果沒有synchronize修飾

A
B
1001
1001
複製程式碼

是沒有序列執行的

public synchronized void A()
public synchronized void B()複製程式碼

修飾例項方法之後

A
1001
B
2000
複製程式碼

可以看到是序列執行,有鎖的效果

public class Demo21 {

    public static void main(String[] age){
        A a = new A();
        A a1 = new A();
        new Thread(new Runnable() {
            @Override
            public void run() {
                long l = System.currentTimeMillis();
                a.A();
                long l2 = System.currentTimeMillis() - l;
                System.out.println(l2);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                long l = System.currentTimeMillis();
                a1.B();
                long l2 = System.currentTimeMillis() - l;
                System.out.println(l2);
            }
        }).start();
    }

}複製程式碼

如果是不同的例項執行A B方法

A
B
1001
1001
複製程式碼

這樣就可以知道,修飾非靜態例項方法的鎖,就是它的例項物件

修飾靜態方法:

public static synchronized void B()
public static synchronized void A()

複製程式碼

修飾靜態方法之後

A
B
1000
2000
複製程式碼

建立兩個例項,也是序列執行,這樣可以證明是,當前類加鎖,進入同步程式碼塊錢要獲取當前類物件的鎖

修飾程式碼塊

public void A(){
    synchronized (A.class){
        System.out.println("A");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

public void B(){
    synchronized (A.class) {
        System.out.println("B");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}複製程式碼

程式碼執行後

A
B
1001
2000
複製程式碼

在synchronize(物件.class)這個物件就是這個同步方法的鎖了

這裡就是物件synchronize做了一下簡單的認識,其實還有很多複雜的東西,要了解synchronize還需要知道jvm,作業系統等等。但能力不足,就介紹到這裡。

推薦三篇文章:

synchronize三中用法

java synchronize原理總結

記憶體可見性


相關文章