自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 前端
從上文中我們可以得知,在多線程環(huán)境下,恰當?shù)氖褂胹ynchronized關(guān)鍵字可以保證線程同步,使程序的運行結(jié)果與預(yù)期一致。

01、背景介紹

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

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

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) {
        // 初始化數(shù)據(jù)實體
        DataEntity entity = new DataEntity();
        //使用多線程編程對數(shù)據(jù)進行計算
        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());
    }
}

多次運行結(jié)果如下:

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

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

但是多次運行結(jié)果都不是這個數(shù),而且每次運行結(jié)果都不一樣,為什么會出現(xiàn)這個結(jié)果呢?

簡單的說,這是主內(nèi)存和線程的工作內(nèi)存數(shù)據(jù)不一致,以及多線程執(zhí)行時無序,共同造成的結(jié)果!

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

圖片圖片

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

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

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

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

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

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

public class DataEntity {

    private int count = 0;

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

    public int getCount(){
        return count;
    }
}

多次運行結(jié)果如下:

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

運行結(jié)果與預(yù)期一致!

03、synchronized 使用詳解

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

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

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

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

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

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

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

3.1、修飾一個方法

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

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

示例如下:

public class DataEntity {

    private int count;

    /**
     * 在方法上加上 synchronized 關(guān)鍵字
     */
    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) {
        // 初始化數(shù)據(jù)實體
        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());
    }
}

運行結(jié)果如下:

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

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

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

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());
    }
}

運行結(jié)果如下:

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

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

3.2、修飾一個靜態(tài)的方法

synchronized修改一個靜態(tài)的方法時,代表的是對當前.java文件對應(yīng)的 Class 類加鎖,不區(qū)分對象實例。

示例如下:

public class DataEntity {

    private static int count;

    /**
     * 在靜態(tài)方法上加上 synchronized 關(guān)鍵字
     */
    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());
    }
}

運行結(jié)果如下:

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

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

3.3、修飾一個代碼塊

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

示例如下

public class DataEntity {

    private int count;

    /**
     * 在方法上加上 synchronized 關(guān)鍵字
     */
    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) {
        // 初始化數(shù)據(jù)實體
        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());
    }
}

運行結(jié)果如下:

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()還可以修飾任意實例對象,作用的范圍就是具體的實例對象。

比如,修飾個自定義的類實例對象,作用的范圍是擁有l(wèi)ock對象,其實也等價于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修飾代碼塊,比較經(jīng)典的應(yīng)用案例,就是單例設(shè)計模式中的雙重校驗鎖實現(xiàn)。

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;  
    }  
}

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

04、synchronized 鎖重入介紹

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

我們看個例子就能明白。

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) {
        // 初始化數(shù)據(jù)實體
        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());
    }
}

運行結(jié)果如下:

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

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

04、小結(jié)

從上文中我們可以得知,在多線程環(huán)境下,恰當?shù)氖褂胹ynchronized關(guān)鍵字可以保證線程同步,使程序的運行結(jié)果與預(yù)期一致。

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

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

責任編輯:武曉燕 來源: 潘志的研發(fā)筆記
相關(guān)推薦

2021-06-02 15:30:12

Synchronize并發(fā)多線程

2022-04-11 07:40:45

synchroniz靜態(tài)方法程序

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

2021-08-21 16:13:29

騰訊老年版手機銀行

2011-06-09 15:15:52

RAII

2012-03-22 09:31:14

Java

2009-07-21 17:41:58

JDBC數(shù)據(jù)源

2022-06-16 07:31:15

MySQL服務(wù)器服務(wù)

2019-04-29 11:00:14

架構(gòu)負載均衡互聯(lián)網(wǎng)

2022-09-13 09:31:59

Python內(nèi)置函數(shù)lambda

2024-03-14 08:17:33

JVMJava對象

2023-12-27 12:12:35

NumPy函數(shù)數(shù)組

2016-03-23 10:35:31

交互可控干貨

2009-01-14 09:28:12

OracleSQL10g

2021-12-14 14:50:12

synchronizeJava

2021-02-07 07:40:31

Synchronize用法

2023-12-29 08:37:59

點贊
收藏

51CTO技術(shù)棧公眾號