java基礎之線程 認識一下synchronize

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

線程部分:緩存

存在都是有理由的。若是沒有線程咱們的程序只能是下圖這個樣子,想象一個下假若有1千萬個請求,每一個請求1秒,這得請求多長時間
bash


若是不止一個窗口處理事情,這個時候,線程的優勢就體現出來了,這樣執行完這麼多請求就除4了多線程


有點是會體現出來,但同時也暴露出了多線程的不足。一個CPU只能運行一個線程。四核也就是能夠運行4個線程,平時看這開100個線程也沒事,一下就執行完了,可是這些CPU運行的時間片斷過短,執行快因此看上去也沒事,可是若是超過了必定範疇也會出問題,(這不是本章的重點),還有就是數據容易成爲髒數據,若是多個線程去修改同一個int,那麼這個字符串最終是什麼樣呢?還有各類搶佔資源,死鎖...等等。jvm

本章重點說的是髒數據問題。其餘的後續更新ide

實例,多線程下髒數據的出現:spa

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操作系統

可是結果是什麼樣呢?.net

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++很短,執行很快爲何仍是會出現這個狀況。線程


簡單看一下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實現

內存可見性:一個線程對共享變量值的修改,可以及時被其餘線程看到。

操做原子性:持有同一個鎖的兩個同步快只能串行進入


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原理總結

內存可見性

相關文章
相關標籤/搜索