成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

synchronized用法簡單梳理

lindroid / 907人閱讀

摘要:共享資源臨界資源修飾實(shí)例方法輸出結(jié)果上述代碼與前面不同的是我們同時(shí)創(chuàng)建了兩個新實(shí)例,然后啟動兩個不同的線程對共享變量進(jìn)行操作,但很遺憾操作結(jié)果是而不是期望結(jié)果。

線程安全是并發(fā)編程中的重要關(guān)注點(diǎn),應(yīng)該注意到的是,造成線程安全問題的主要誘因有兩點(diǎn)

一是存在共享數(shù)據(jù)(也稱臨界資源)

二是存在多條線程共同操作共享數(shù)據(jù)

因此為了解決這個問題,我們可能需要這樣一個方案,當(dāng)存在多個線程操作共享數(shù)據(jù)時(shí),需要保證同一時(shí)刻有且只有一個線程在操作共享數(shù)據(jù),其他線程必須等到該線程處理完數(shù)據(jù)后再進(jìn)行,這種方式有個高尚的名稱叫互斥鎖,即能達(dá)到互斥訪問目的的鎖,也就是說當(dāng)一個共享數(shù)據(jù)被當(dāng)前正在訪問的線程加上互斥鎖后,在同一個時(shí)刻,其他線程只能處于等待的狀態(tài),直到當(dāng)前線程處理完畢釋放該鎖。
在 Java 中,關(guān)鍵字 synchronized 可以保證在同一個時(shí)刻,只有一個線程可以執(zhí)行某個方法或者某個代碼塊(主要是對方法或者代碼塊中存在共享數(shù)據(jù)的操作),同時(shí)我們還應(yīng)該注意到synchronized另外一個重要的作用,synchronized可保證一個線程的變化(主要是共享數(shù)據(jù)的變化)被其他線程所看到(保證可見性,完全可以替代Volatile功能),這點(diǎn)確實(shí)也是很重要的。

synchronized的三種應(yīng)用方式

synchronized關(guān)鍵字最主要有以下3種應(yīng)用方式,下面分別介紹

修飾實(shí)例方法,作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得 當(dāng)前實(shí)例 的鎖

修飾靜態(tài)方法,作用于當(dāng)前類對象加鎖,進(jìn)入同步代碼前要獲得 當(dāng)前類對象 的鎖

修飾代碼塊,指定加鎖對象,對給定對象加鎖,進(jìn)入同步代碼庫前要獲得 給定對象 的鎖。

synchronized作用于實(shí)例方法

所謂的實(shí)例對象鎖就是用synchronized修飾實(shí)例對象中的實(shí)例方法,注意是實(shí)例方法不包括靜態(tài)方法

package sychronized;

import static net.mindview.util.Print.*;

import java.util.concurrent.*;

public class AccountingSync2 implements Runnable {
    //共享資源(臨界資源)
    static int i = 0;

    /**
     * synchronized 修飾實(shí)例方法
     */
    synchronized void getI() {
        if (i % 1000000 == 0) {
            print(i);
        }
    }

    public synchronized void increase() {
        i++;
        getI();
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
        print(i);
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        AccountingSync2 accountingSync2 = new AccountingSync2();
        exec.execute(accountingSync2);
        exec.execute(accountingSync2);
        exec.shutdown();
    }
}

最后的結(jié)果為:
1000000
1519541
2000000
2000000

上述代碼中,我們開啟兩個線程操作同一個共享資源即變量i,由于i++操作并不具備原子性,該操作是先讀取值,然后寫回一個新值,相當(dāng)于原來的值加上1,分兩步完成,如果第二個線程在第一個線程讀取舊值和寫回新值期間讀取i的域值,那么第二個線程就會與第一個線程一起看到同一個值,并執(zhí)行相同值的加1操作,這也就造成了線程安全失敗,因此對于increase方法必須使用synchronized修飾,以便保證線程安全。

此時(shí)注意到synchronized修飾的是實(shí)例方法increase,在這樣的情況下,當(dāng)前線程的鎖便是實(shí)例對象instance,注意Java中的線程同步鎖可以是任意對象。

當(dāng)一個線程正在訪問一個對象的 synchronized 實(shí)例方法,那么其他線程不能訪問該對象的其他 synchronized 方法,畢竟一個對象只有一把鎖,當(dāng)一個線程獲取了該對象的鎖之后,其他線程無法獲取該對象的鎖,所以無法訪問該對象的其他synchronized實(shí)例方法,但是其他線程還是可以訪問該實(shí)例對象的其他非synchronized方法,但是一個 synchronized 方法可以調(diào)用另一個需要獲得同樣鎖的synchronized方法,因?yàn)橐呀?jīng)獲取了鎖。

如果是一個線程 A 需要訪問實(shí)例對象 obj1 的 synchronized 方法 f1(當(dāng)前對象鎖是obj1),另一個線程 B 需要訪問實(shí)例對象 obj2 的 synchronized 方法f2(當(dāng)前對象鎖是obj2),這樣是允許的,因?yàn)閮蓚€實(shí)例對象鎖并不同相同,此時(shí)如果兩個線程操作數(shù)據(jù)并非共享的,線程安全是有保障的,遺憾的是如果兩個線程操作的是共享數(shù)據(jù),那么線程安全就有可能無法保證了。

package sychronized;

import static net.mindview.util.Print.*;
import java.util.concurrent.*;

public class AccountingSync2 implements Runnable {
    //共享資源(臨界資源)
    static int i = 0;

    /**
     * synchronized 修飾實(shí)例方法
     */
    synchronized void getI() {
        if (i % 1000000 == 0) {
            print(i);
        }
    }

    public synchronized void increase() {
        i++;
        getI();
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
        print(i);
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        AccountingSync2 accountingSync2 = new AccountingSync2();
        exec.execute(accountingSync2);
        exec.execute(new AccountingSync2());
        exec.shutdown();
    }
}

#輸出結(jié)果:
1000000
1249050
1329218

上述代碼與前面不同的是我們同時(shí)創(chuàng)建了兩個新實(shí)例AccountingSync2,然后啟動兩個不同的線程對共享變量i進(jìn)行操作,但很遺憾操作結(jié)果是1329218而不是期望結(jié)果2000000。

雖然我們使用synchronized修飾了increase方法,但卻new了兩個不同的實(shí)例對象,這也就意味著存在著兩個不同的實(shí)例對象鎖,因此兩個進(jìn)程都會進(jìn)入各自持有對象的對象鎖,也就是說兩個線程使用的是不同的鎖,因此線程安全是無法保證的。

解決這種困境的的方式是將synchronized作用于靜態(tài)的increase方法,這樣的話,對象鎖就當(dāng)前類對象,由于無論創(chuàng)建多少個實(shí)例對象,但對于的類對象擁有只有一個,所有在這樣的情況下對象鎖就是唯一的。下面我們看看如何使用將synchronized作用于靜態(tài)的increase方法。

synchronized作用于靜態(tài)方法

當(dāng)synchronized作用于靜態(tài)方法時(shí),其鎖就是當(dāng)前類的class對象鎖。由于靜態(tài)成員不專屬于任何一個實(shí)例對象,是類成員,因此通過class對象鎖可以控制靜態(tài)成員的并發(fā)操作。需要注意的是如果一個線程A調(diào)用一個實(shí)例對象的非static synchronized 方法,而線程B需要調(diào)用這個實(shí)例對象所屬類的靜態(tài) synchronized方法,是允許的,不會發(fā)生互斥現(xiàn)象,因?yàn)?strong>訪問靜態(tài) synchronized 方法占用的鎖是當(dāng)前類的 class 對象,而訪問非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實(shí)例對象鎖。

package sychronized;

import static net.mindview.util.Print.*;
import java.util.concurrent.*;

class OtherTask implements Runnable{
    AccountingSyncClass accounting = new AccountingSyncClass();
    @Override
    public void run(){
        for (int j = 0; j < 1000000; j++) {
            accounting.increaseForObject();
        }
        print(accounting.getI());
    }
}

public class AccountingSyncClass implements Runnable {
    //共享資源(臨界資源)
    private static int i = 0;

    /**
     * synchronized 修飾實(shí)例方法
     */
    public synchronized void increaseForObject() {
        i++;
    }

    public synchronized static void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
        print(i);
    }
    
    public int getI(){
        return i;
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new AccountingSyncClass());
        exec.execute(new AccountingSyncClass());
        exec.execute(new OtherTask()); // 1
        exec.shutdown();
    }
}

輸出結(jié)果為:
1459696
2692181
2754098

注釋掉代碼中的 1 那一行代碼的輸出結(jié)果為:
1468495
2000000

由于synchronized關(guān)鍵字修飾的是靜態(tài)increase方法,與修飾實(shí)例方法不同的是,其鎖對象是當(dāng)前類的class對象。

注意代碼中的increase4Obj方法是實(shí)例方法,其對象鎖是當(dāng)前實(shí)例對象,如果別的線程調(diào)用該方法,將不會產(chǎn)生互斥現(xiàn)象,畢竟鎖對象不同,但我們應(yīng)該意識到這種情況下可能會發(fā)現(xiàn)線程安全問題(操作了共享靜態(tài)變量i)。

因此在設(shè)計(jì)同步代碼的時(shí)候一定要仔細(xì)思考到底該用 多大的同步粒度 和 該對什么對象 使用同步操作。

synchronized同步代碼塊

除了使用關(guān)鍵字修飾實(shí)例方法和靜態(tài)方法外,還可以使用同步代碼塊,在某些情況下,我們編寫的方法體可能比較大,同時(shí)存在一些比較耗時(shí)的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進(jìn)行同步操作,可能會得不償失,此時(shí)我們可以使用同步代碼塊的方式對需要同步的代碼進(jìn)行包裹,這樣就無需對整個方法進(jìn)行同步操作了。

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run() {
        //省略其他耗時(shí)操作....
        //使用同步代碼塊對變量i進(jìn)行同步操作,鎖對象為instance
        synchronized(instance){
            for(int j=0;j<1000000;j++){
                    i++;
              }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}

從代碼看出,將synchronized作用于一個給定的實(shí)例對象instance,即當(dāng)前實(shí)例對象就是鎖對象,每次當(dāng)線程進(jìn)入synchronized包裹的代碼塊時(shí)就會要求當(dāng)前線程持有instance實(shí)例對象鎖。

如果當(dāng)前有其他線程正持有該對象鎖,那么新到的線程就必須等待,這樣也就保證了每次只有一個線程執(zhí)行i++操作。

當(dāng)然除了instance作為對象外,我們還可以使用this對象(代表當(dāng)前實(shí)例)或者當(dāng)前類的class對象作為鎖。

//this,當(dāng)前實(shí)例對象鎖
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

//class對象鎖
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
同步方法最好運(yùn)用在 共享資源 內(nèi)部而不是使用它的外部
package sychronized;

import static net.mindview.util.Print.*;
import java.util.concurrent.*;

class OtherTask implements Runnable{
    AccountingSyncClass accounting = new AccountingSyncClass();
    @Override
    public void run(){
        for (int j = 0; j < 1000000; j++) {
            accounting.increaseForObject();
        }
        print(accounting.getAdapterInteger());
    }
}

class AdapterInteger {
    private int i = 0;
    public synchronized void increase(){
        ++i;
    }
    public synchronized int getI(){
        return i;
    }
}

public class AccountingSyncClass implements Runnable {
    //共享資源(臨界資源)
    private static AdapterInteger adapterInteger = new AdapterInteger();

    /**
     * synchronized 修飾實(shí)例方法
     */
    public synchronized void increaseForObject() {
        adapterInteger.increase();
    }

    public synchronized static void increase() {
        adapterInteger.increase();
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
        print(getAdapterInteger());
    }

    public static int getAdapterInteger() {
        return adapterInteger.getI();
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new AccountingSyncClass());
        exec.execute(new AccountingSyncClass());
        exec.execute(new OtherTask());
        exec.shutdown();
    }
}

#輸出結(jié)果為
1183139
2688189
3000000

這樣三個線程中的任務(wù)的鎖都是我們的共享變量 adapterInteger 對象的鎖,這樣就可以完成真正的同步,不管哪個線程都是獲得了 adapterInteger 對象的鎖才能運(yùn)行相應(yīng)的代碼。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/70656.html

相關(guān)文章

  • Java 多線程核心技術(shù)梳理(附源碼)

    摘要:本文對多線程基礎(chǔ)知識進(jìn)行梳理,主要包括多線程的基本使用,對象及變量的并發(fā)訪問,線程間通信,的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。源碼采用構(gòu)建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。 本文對多線程基礎(chǔ)知識進(jìn)行梳理,主要包括多線程的基本使用,對象及變量的并發(fā)訪問,線程間通信,lock的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。 寫在前面 花了一周時(shí)...

    Winer 評論0 收藏0
  • 到底什么是重入鎖,拜托,一次搞清楚!

    摘要:為什么叫重入鎖呢,我們把它拆開來看就明了了。釋放鎖,每次鎖持有者數(shù)量遞減,直到為止。 相信大家在工作或者面試過程中經(jīng)常聽到重入鎖這個概念,或者與關(guān)鍵字 synchrozied 的對比,棧長面試了這么多人,80%的面試者都沒有答對或沒有答到點(diǎn)上,或者把雙重效驗(yàn)鎖搞混了,哭笑不得。。 那么你對重入鎖了解有多少呢?今天,棧長幫大家撕開重入鎖的面紗,來見識下重入鎖的真實(shí)容顏。。 什么是重入鎖 ...

    LiuRhoRamen 評論0 收藏0
  • JAVA線程狀態(tài)梳理以及Jstack命令使用

    摘要:先上一張線程狀態(tài)轉(zhuǎn)換圖做如下說明代碼中共有除之外的種狀態(tài),為了表示線程正在執(zhí)行,特加了這種狀態(tài)。但是由于和以及用于協(xié)調(diào)對共享資源的存取,所以必須在塊中使用。所以即便狀態(tài)的線程被喚醒了,也需要再次獲得鎖,所以喚醒后進(jìn)入狀態(tài)。 1.先上一張線程狀態(tài)轉(zhuǎn)換圖 showImg(https://segmentfault.com/img/bVJQ8i?w=791&h=424); 做如下說明:代碼中共...

    itvincent 評論0 收藏0
  • 淺談Java中鎖的問題

    摘要:之前我們簡單的討論了一下關(guān)于中的同步還有一些鎖優(yōu)化的問題,今天我們就來簡單的聊一聊關(guān)于中的死鎖問題。這里顯示兩個線程的狀態(tài)現(xiàn)在是處于阻塞狀態(tài),然后都在等待鎖的獲取,我們再繼續(xù)往下看。 之前我們簡單的討論了一下關(guān)于Java中的同步還有一些鎖優(yōu)化的問題,今天我們就來簡單的聊一聊關(guān)于Java中的死鎖問題。 這個問題我們在開發(fā)的時(shí)候,或多或少都能遇到,對業(yè)務(wù)邏輯沒有正確的梳理,又或者是在多線程...

    fox_soyoung 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<