成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

淺談 Synchronized 的幾種用法,超多干貨!

開發 前端
從上文中我們可以得知,在多線程環境下,恰當的使用synchronized關鍵字可以保證線程同步,使程序的運行結果與預期一致。

01、背景介紹

說到并發編程,總繞不開線程安全的問題。

實際上,在多線程環境中,難免會出現多個線程對一個對象的實例變量進行同時訪問和操作,如果編程處理不當,會產生臟讀現象。

02、線程安全問題回顧

我們先來看一個簡單的線程安全問題的例子!

public class DataEntity {

    private int count = 0;

    public void addCount(){
        count++;
    }

    public int getCount(){
        return count;
    }
}
public class MyThread extends Thread {

    private DataEntity entity;

    public MyThread(DataEntity entity) {
        this.entity = entity;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            entity.addCount();
        }
    }
}
public class MyThreadTest {

    public static void main(String[] args) {
        // 初始化數據實體
        DataEntity entity = new DataEntity();
        //使用多線程編程對數據進行計算
        for (int i = 0; i < 10; i++) {
            MyThread thread = new MyThread(entity);
            thread.start();
        }

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + entity.getCount());
    }
}

多次運行結果如下:

第一次運行:result: 9788554
第二次運行:result: 9861461
第三次運行:result: 6412249
...

上面的代碼中,總共開啟了 10 個線程,每個線程都累加了 1000000 次,如果結果正確的話,自然而然總數就應該是 10 * 1000000 = 10000000。

但是多次運行結果都不是這個數,而且每次運行結果都不一樣,為什么會出現這個結果呢?

簡單的說,這是主內存和線程的工作內存數據不一致,以及多線程執行時無序,共同造成的結果!

我們先簡單的了解一下 Java 的內存模型,后期我們在介紹里面的原理!

圖片圖片

如上圖所示,線程 A 和線程 B 之間,如果要完成數據通信的話,需要經歷以下幾個步驟:

  • 1.線程 A 從主內存中將共享變量讀入線程 A 的工作內存后并進行操作,之后將數據重新寫回到主內存中;
  • 2.線程 B 從主存中讀取最新的共享變量,然后存入自己的工作內存中,再進行操作,數據操作完之后再重新寫入到主內存中;

如果線程 A 更新后數據并沒有及時寫回到主存,而此時線程 B 從主內存中讀到的數據,可能就是過期的數據,于是就會出現“臟讀”現象。

因此在多線程環境下,如果不進行一定干預處理,可能就會出現像上文介紹的那樣,采用多線程編程時,程序的實際運行結果與預期會不一致,就會產生非常嚴重的問題。

針對多線程編程中,程序運行不安全的問題,Java 提供了synchronized關鍵字來解決這個問題,當多個線程同時訪問共享資源時,會保證線程依次排隊操作共享變量,從而保證程序的實際運行結果與預期一致。

我們對上面示例中的DataEntity.addCount()方法進行改造,再看看效果如下。

public class DataEntity {

    private int count = 0;

    /**
     * 在方法上加上 synchronized 關鍵字
     */
    public synchronized void addCount(){
        count++;
    }

    public int getCount(){
        return count;
    }
}

多次運行結果如下:

第一次運行:result: 10000000
第二次運行:result: 10000000
第三次運行:result: 10000000
...

運行結果與預期一致!

03、synchronized 使用詳解

synchronized作為 Java 中的關鍵字,在多線程編程中,有著非常重要的地位,也是新手了解并發編程的基礎,從功能角度看,它有以下幾個比較重要的特性:

  • 原子性:即一個或多個操作要么全部執行成功,要么全部執行失敗。synchronized關鍵字可以保證只有一個線程拿到鎖,訪問共享資源
  • 可見性:即一個線程對共享變量進行修改后,其他線程可以立刻看到。執行synchronized時,線程獲取鎖之后,一定從主內存中讀取數據,釋放鎖之前,一定會將數據寫回主內存,從而保證內存數據可見性
  • 有序性:即保證程序的執行順序會按照代碼的先后順序執行。synchronized關鍵字,可以保證每個線程依次排隊操作共享變量

synchronized也被稱為同步鎖,它可以把任意一個非 NULL 的對象當成鎖,只有拿到鎖的線程能進入方法體,并且只有一個線程能進入,其他的線程必須等待鎖釋放了才能進入,它屬于獨占式的悲觀鎖,同時也屬于可重入鎖。

關于鎖的知識,我們后面在介紹,大家先了解一下就行。

從實際的使用角度來看,synchronized修飾的對象有以下幾種:

  • 修飾一個方法:被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象
  • 修飾一個靜態的方法:其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象
  • 修飾一個代碼塊:被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象,使用上比較靈活

下面我們一起來看看它們的具體用法。

3.1、修飾一個方法

當synchronized修飾一個方法時,多個線程訪問同一個對象,哪個線程持有該方法所屬對象的鎖,就擁有執行權限,否則就只能等待。

如果多線程訪問的不是同一個對象,不會起到保證線程同步的作用。

示例如下:

public class DataEntity {

    private int count;

    /**
     * 在方法上加上 synchronized 關鍵字
     */
    public synchronized void addCount(){
        for (int i = 0; i < 3; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public int getCount() {
        return count;
    }
}
public class MyThreadA extends Thread {

    private DataEntity entity;

    public MyThreadA(DataEntity entity) {
        this.entity = entity;
    }

    @Override
    public void run() {
        entity.addCount();
    }
}
public class MyThreadB extends Thread {

    private DataEntity entity;

    public MyThreadB(DataEntity entity) {
        this.entity = entity;
    }

    @Override
    public void run() {
        entity.addCount();
    }
}
public class MyThreadTest {

    public static void main(String[] args) {
        // 初始化數據實體
        DataEntity entity = new DataEntity();

        MyThreadA threadA = new MyThreadA(entity);
        threadA.start();

        MyThreadB threadB = new MyThreadB(entity);
        threadB.start();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + entity.getCount());
    }
}

運行結果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

當兩個線程共同操作一個對象時,此時每個線程都會依次排隊執行。

假如兩個線程操作的不是一個對象,此時沒有任何效果,示例如下:

public class MyThreadTest {

    public static void main(String[] args) {
        DataEntity entity1 = new DataEntity();
        MyThreadA threadA = new MyThreadA(entity1);
        threadA.start();

        DataEntity entity2 = new DataEntity();
        MyThreadA threadB = new MyThreadA(entity2);
        threadB.start();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + entity1.getCount());
        System.out.println("result: " + entity2.getCount());
    }
}

運行結果如下:

Thread-0:0
Thread-1:0
Thread-0:1
Thread-1:1
Thread-0:2
Thread-1:2
result: 3
result: 3

從結果上可以看出,當synchronized修飾一個方法,當多個線程訪問同一個對象的方法,每個線程會依次排隊;如果訪問的不是一個對象,線程不會進行排隊,像正常執行一樣。

3.2、修飾一個靜態的方法

synchronized修改一個靜態的方法時,代表的是對當前.java文件對應的 Class 類加鎖,不區分對象實例。

示例如下:

public class DataEntity {

    private static int count;

    /**
     * 在靜態方法上加上 synchronized 關鍵字
     */
    public synchronized static void addCount(){
        for (int i = 0; i < 3; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static int getCount() {
        return count;
    }
}
public class MyThreadA extends Thread {

    @Override
    public void run() {
        DataEntity.addCount();
    }
}
public class MyThreadB extends Thread {

    @Override
    public void run() {
        DataEntity.addCount();
    }
}
public class MyThreadTest {

    public static void main(String[] args) {

        MyThreadA threadA = new MyThreadA();
        threadA.start();

        MyThreadB threadB = new MyThreadB();
        threadB.start();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + DataEntity.getCount());
    }
}

運行結果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

靜態同步方法和非靜態同步方法持有的是不同的鎖,前者是類鎖,后者是對象鎖,類鎖可以理解為這個類的所有對象。

3.3、修飾一個代碼塊

synchronized用于修飾一個代碼塊時,只會控制代碼塊內的執行順序,其他試圖訪問該對象的線程將被阻塞,編程比較靈活,在實際開發中用的應用比較廣泛。

示例如下

public class DataEntity {

    private int count;

    /**
     * 在方法上加上 synchronized 關鍵字
     */
    public void addCount(){
        synchronized (this){
            for (int i = 0; i < 3; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public int getCount() {
        return count;
    }
}
public class MyThreadTest {

    public static void main(String[] args) {
        // 初始化數據實體
        DataEntity entity = new DataEntity();

        MyThreadA threadA = new MyThreadA(entity);
        threadA.start();

        MyThreadB threadB = new MyThreadB(entity);
        threadB.start();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + entity.getCount());
    }
}

運行結果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

其中synchronized (this)中的this,表示的是當前類實例的對象,效果等同于public synchronized void addCount()。

除此之外,synchronized()還可以修飾任意實例對象,作用的范圍就是具體的實例對象。

比如,修飾個自定義的類實例對象,作用的范圍是擁有lock對象,其實也等價于synchronized (this)。

public class DataEntity {

    private Object lock = new Object();

    /**
     * synchronized 可以修飾任意實例對象
     */
    public void addCount(){
        synchronized (lock){
            // todo...
        }
    }
}

當然也可以用于修飾類,表示類鎖,效果等同于public synchronized static void addCount()。

public class DataEntity {
    
    /**
     * synchronized 可以修飾類,表示類鎖
     */
    public void addCount(){
        synchronized (DataEntity.class){
            // todo...
        }
    }
}

synchronized修飾代碼塊,比較經典的應用案例,就是單例設計模式中的雙重校驗鎖實現。

public class Singleton {  

    private volatile static Singleton singleton;  
    
    private Singleton (){}  
    
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

采用代碼塊的實現方式,編程會更加靈活,可以顯著的提升并發查詢的效率。

04、synchronized 鎖重入介紹

synchronized關鍵字擁有鎖重入的功能,所謂鎖重入的意思就是:當一個線程得到一個對象鎖后,再次請求此對象鎖時可以再次得到該對象的鎖,而無需等待。

我們看個例子就能明白。

public class DataEntity {

    private int count = 0;

    
    public synchronized void addCount1(){
        System.out.println(Thread.currentThread().getName() + ":" + (count++));
        addCount2();
    }

    public synchronized void addCount2(){
        System.out.println(Thread.currentThread().getName() + ":" + (count++));
        addCount3();
    }

    public synchronized void addCount3(){
        System.out.println(Thread.currentThread().getName() + ":" + (count++));

    }

    public int getCount() {
        return count;
    }
}
public class MyThreadA extends Thread {

    private DataEntity entity;

    public MyThreadA(DataEntity entity) {
        this.entity = entity;
    }

    @Override
    public void run() {
        entity.addCount1();
    }
}
public class MyThreadB extends Thread {

    private DataEntity entity;

    public MyThreadB(DataEntity entity) {
        this.entity = entity;
    }

    @Override
    public void run() {
        entity.addCount1();
    }
}
public class MyThreadTest {

    public static void main(String[] args) {
        // 初始化數據實體
        DataEntity entity = new DataEntity();

        MyThreadA threadA = new MyThreadA(entity);
        threadA.start();

        MyThreadB threadB = new MyThreadB(entity);
        threadB.start();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + entity.getCount());
    }
}

運行結果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

從結果上看線程沒有交替執行,線程Thread-0獲取到鎖之后,再次調用其它帶有synchronized關鍵字的方法時,可以快速進入,而Thread-1線程需等待對象鎖完全釋放之后再獲取,這就是鎖重入。

04、小結

從上文中我們可以得知,在多線程環境下,恰當的使用synchronized關鍵字可以保證線程同步,使程序的運行結果與預期一致。

  • 1.當synchronized修飾一個方法時,作用的范圍是整個方法,作用的對象是調用這個方法的對象;
  • 2..當synchronized修飾一個靜態方法時,作用的范圍是整個靜態方法,作用的對象是這個類的所有對象;
  • 3.當synchronized修飾一個代碼塊時,作用的范圍是代碼塊,作用的對象是修飾的內容,如果是類,則這個類的所有對象都會受到控制;如果是任意對象實例子,則控制的是具體的對象實例,誰擁有這個對象鎖,就能進入方法體

synchronized是一種同步鎖,屬于獨占式,使用它進行線程同步,JVM 性能開銷很大,大量的使用未必會帶來好處。

責任編輯:武曉燕 來源: 潘志的研發筆記
相關推薦

2021-06-02 15:30:12

Synchronize并發多線程

2022-04-11 07:40:45

synchroniz靜態方法程序

2022-06-29 08:16:55

對象String字面量

2023-08-26 11:32:07

2011-06-20 10:36:29

SEO

2024-04-24 10:24:09

2011-06-09 15:15:52

RAII

2021-08-21 16:13:29

騰訊老年版手機銀行

2009-07-21 17:41:58

JDBC數據源

2012-03-22 09:31:14

Java

2022-06-16 07:31:15

MySQL服務器服務

2024-03-14 08:17:33

JVMJava對象

2019-04-29 11:00:14

架構負載均衡互聯網

2022-09-13 09:31:59

Python內置函數lambda

2023-12-27 12:12:35

NumPy函數數組

2016-03-23 10:35:31

交互可控干貨

2011-03-21 13:44:38

SQL ServerSQL Server2分頁

2009-07-28 16:07:40

.NET圖片快速處理

2009-01-14 09:28:12

OracleSQL10g

2010-04-19 09:52:24

Oracle行級鎖
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人在线观看免费 | 欧美日韩国产在线观看 | 中文字幕三区 | 日韩在线一区二区三区 | 天天操网 | 欧美日韩中文字幕在线播放 | 91久久精品国产91久久性色tv | 在线观看免费毛片 | www视频在线观看 | 亚洲欧美精品 | 五十女人一级毛片 | 91素人| 热99在线| 天天摸天天干 | 涩涩99| 国产一区二区精品 | 成人午夜在线 | 91欧美精品成人综合在线观看 | 美女福利网站 | 免费人成激情视频在线观看冫 | 国产精品久久久久久久午夜 | 国产精品视频一区二区三区四区国 | 久热免费在线 | 9porny九色视频自拍 | 成人无遮挡毛片免费看 | 精品影院| 国产精品久久久久久久模特 | 欧美aaaaa| 色资源在线 | 国产精品高清在线 | 免费观看一级视频 | 久久久女女女女999久久 | 中文字幕在线播放第一页 | 国产精品一区二区在线 | 国产精品久久久久久久久久久久久 | 在线看亚洲 | 国产一级一片免费播放 | 久久av一区二区三区 | 91传媒在线播放 | 久久夜视频| 欧美成年黄网站色视频 |