最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解java安全編碼指南之可見性和原子性

 更新時(shí)間:2021年06月03日 10:12:05   作者:flydean  
java類中會(huì)定義很多變量,有類變量也有實(shí)例變量,這些變量在訪問(wèn)的過(guò)程中,會(huì)遇到一些可見性和原子性的問(wèn)題。這里我們來(lái)詳細(xì)了解一下怎么避免這些問(wèn)題。

不可變對(duì)象的可見性

不可變對(duì)象就是初始化之后不能夠被修改的對(duì)象,那么是不是類中引入了不可變對(duì)象,所有對(duì)不可變對(duì)象的修改都立馬對(duì)所有線程可見呢?

實(shí)際上,不可變對(duì)象只能保證在多線程環(huán)境中,對(duì)象使用的安全性,并不能夠保證對(duì)象的可見性。

先來(lái)討論一下可變性,我們考慮下面的一個(gè)例子:

public final class ImmutableObject {
    private final int age;
    public ImmutableObject(int age){
        this.age=age;
    }
}

我們定義了一個(gè)ImmutableObject對(duì)象,class是final的,并且里面的唯一字段也是final的。所以這個(gè)ImmutableObject初始化之后就不能夠改變。

然后我們定義一個(gè)類來(lái)get和set這個(gè)ImmutableObject:

public class ObjectWithNothing {
    private ImmutableObject refObject;
    public ImmutableObject getImmutableObject(){
        return refObject;
    }
    public void setImmutableObject(int age){
        this.refObject=new ImmutableObject(age);
    }
}

上面的例子中,我們定義了一個(gè)對(duì)不可變對(duì)象的引用refObject,然后定義了get和set方法。

注意,雖然ImmutableObject這個(gè)類本身是不可變的,但是我們對(duì)該對(duì)象的引用refObject是可變的。這就意味著我們可以調(diào)用多次setImmutableObject方法。

再來(lái)討論一下可見性。

上面的例子中,在多線程環(huán)境中,是不是每次setImmutableObject都會(huì)導(dǎo)致getImmutableObject返回一個(gè)新的值呢?

答案是否定的。

當(dāng)把源碼編譯之后,在編譯器中生成的指令的順序跟源碼的順序并不是完全一致的。處理器可能采用亂序或者并行的方式來(lái)執(zhí)行指令(在JVM中只要程序的最終執(zhí)行結(jié)果和在嚴(yán)格串行環(huán)境中執(zhí)行結(jié)果一致,這種重排序是允許的)。并且處理器還有本地緩存,當(dāng)將結(jié)果存儲(chǔ)在本地緩存中,其他線程是無(wú)法看到結(jié)果的。除此之外緩存提交到主內(nèi)存的順序也肯能會(huì)變化。

怎么解決呢?

最簡(jiǎn)單的解決可見性的辦法就是加上volatile關(guān)鍵字,volatile關(guān)鍵字可以使用java內(nèi)存模型的happens-before規(guī)則,從而保證volatile的變量修改對(duì)所有線程可見。

public class ObjectWithVolatile {
    private volatile ImmutableObject refObject;
    public ImmutableObject getImmutableObject(){
        return refObject;
    }
    public void setImmutableObject(int age){
        this.refObject=new ImmutableObject(age);
    }
}

另外,使用鎖機(jī)制,也可以達(dá)到同樣的效果:

public class ObjectWithSync {
    private  ImmutableObject refObject;
    public synchronized ImmutableObject getImmutableObject(){
        return refObject;
    }
    public synchronized void setImmutableObject(int age){
        this.refObject=new ImmutableObject(age);
    }
}

最后,我們還可以使用原子類來(lái)達(dá)到同樣的效果:

public class ObjectWithAtomic {
    private final AtomicReference<ImmutableObject> refObject= new AtomicReference<>();
    public ImmutableObject getImmutableObject(){
        return refObject.get();
    }
    public void setImmutableObject(int age){
        refObject.set(new ImmutableObject(age));
    }
}

保證共享變量的復(fù)合操作的原子性

如果是共享對(duì)象,那么我們就需要考慮在多線程環(huán)境中的原子性。如果是對(duì)共享變量的復(fù)合操作,比如:++, -- *=, /=, %=, +=, -=, <<=, >>=, >>>=, ^= 等,看起來(lái)是一個(gè)語(yǔ)句,但實(shí)際上是多個(gè)語(yǔ)句的集合。

我們需要考慮多線程下面的安全性。

考慮下面的例子:

public class CompoundOper1 {
    private int i=0;
    public int increase(){
        i++;
        return i;
    }
}

例子中我們對(duì)int i進(jìn)行累加操作。但是++實(shí)際上是由三個(gè)操作組成的:

1.從內(nèi)存中讀取i的值,并寫入CPU寄存器中。

2.CPU寄存器中將i值+1

3.將值寫回內(nèi)存中的i中。

如果在單線程環(huán)境中,是沒有問(wèn)題的,但是在多線程環(huán)境中,因?yàn)椴皇窃硬僮鳎涂赡軙?huì)發(fā)生問(wèn)題。

解決辦法有很多種,第一種就是使用synchronized關(guān)鍵字

public synchronized int increaseSync(){
    i++;
    return i;
}

第二種就是使用lock:

private final ReentrantLock reentrantLock=new ReentrantLock();

public int increaseWithLock(){
    try{
        reentrantLock.lock();
        i++;
        return i;
    }finally {
        reentrantLock.unlock();
    }
}

第三種就是使用Atomic原子類:

private AtomicInteger atomicInteger=new AtomicInteger(0);

public int increaseWithAtomic(){
    return atomicInteger.incrementAndGet();
}

保證多個(gè)Atomic原子類操作的原子性

如果一個(gè)方法使用了多個(gè)原子類的操作,雖然單個(gè)原子操作是原子性的,但是組合起來(lái)就不一定了。

我們看一個(gè)例子:

public class CompoundAtomic {
    private AtomicInteger atomicInteger1=new AtomicInteger(0);
    private AtomicInteger atomicInteger2=new AtomicInteger(0);

    public void update(){
        atomicInteger1.set(20);
        atomicInteger2.set(10);
    }

    public int get() {
        return atomicInteger1.get()+atomicInteger2.get();
    }
}

上面的例子中,我們定義了兩個(gè)AtomicInteger,并且分別在update和get操作中對(duì)兩個(gè)AtomicInteger進(jìn)行操作。

雖然AtomicInteger是原子性的,但是兩個(gè)不同的AtomicInteger合并起來(lái)就不是了。在多線程操作的過(guò)程中可能會(huì)遇到問(wèn)題。

同樣的,我們可以使用同步機(jī)制或者鎖來(lái)保證數(shù)據(jù)的一致性。

保證方法調(diào)用鏈的原子性

如果我們要?jiǎng)?chuàng)建一個(gè)對(duì)象的實(shí)例,而這個(gè)對(duì)象的實(shí)例是通過(guò)鏈?zhǔn)秸{(diào)用來(lái)創(chuàng)建的。那么我們需要保證鏈?zhǔn)秸{(diào)用的原子性。

考慮下面的一個(gè)例子:

public class ChainedMethod {
    private int age=0;
    private String name="";
    private String adress="";

    public ChainedMethod setAdress(String adress) {
        this.adress = adress;
        return this;
    }

    public ChainedMethod setAge(int age) {
        this.age = age;
        return this;
    }

    public ChainedMethod setName(String name) {
        this.name = name;
        return this;
    }
}

很簡(jiǎn)單的一個(gè)對(duì)象,我們定義了三個(gè)屬性,每次set都會(huì)返回對(duì)this的引用。

我們看下在多線程環(huán)境下面怎么調(diào)用:

ChainedMethod chainedMethod= new ChainedMethod();
Thread t1 = new Thread(() -> chainedMethod.setAge(1).setAdress("www.flydean.com1").setName("name1"));
t1.start();

Thread t2 = new Thread(() -> chainedMethod.setAge(2).setAdress("www.flydean.com2").setName("name2"));
t2.start();

因?yàn)樵诙嗑€程環(huán)境下,上面的set方法可能會(huì)出現(xiàn)混亂的情況。

怎么解決呢?我們可以先創(chuàng)建一個(gè)本地的副本,這個(gè)副本因?yàn)槭潜镜卦L問(wèn)的,所以是線程安全的,最后將副本拷貝給新創(chuàng)建的實(shí)例對(duì)象。

主要的代碼是下面樣子的:

public class ChainedMethodWithBuilder {
    private int age=0;
    private String name="";
    private String adress="";

    public ChainedMethodWithBuilder(Builder builder){
        this.adress=builder.adress;
        this.age=builder.age;
        this.name=builder.name;
    }

    public static class Builder{
        private int age=0;
        private String name="";
        private String adress="";

        public static Builder newInstance(){
            return new Builder();
        }
        private Builder() {}

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder setAdress(String adress) {
            this.adress = adress;
            return this;
        }

        public ChainedMethodWithBuilder build(){
            return new ChainedMethodWithBuilder(this);
        }
    }

我們看下怎么調(diào)用:

final ChainedMethodWithBuilder[] builder = new ChainedMethodWithBuilder[1];
Thread t1 = new Thread(() -> {
    builder[0] =ChainedMethodWithBuilder.Builder.newInstance()
        .setAge(1).setAdress("www.flydean.com1").setName("name1")
        .build();});
t1.start();

Thread t2 = new Thread(() ->{
    builder[0] =ChainedMethodWithBuilder.Builder.newInstance()
        .setAge(1).setAdress("www.flydean.com1").setName("name1")
        .build();});
t2.start();

因?yàn)閘ambda表達(dá)式中使用的變量必須是final或者final等效的,所以我們需要構(gòu)建一個(gè)final的數(shù)組。

讀寫64bits的值

在java中,64bits的long和double是被當(dāng)成兩個(gè)32bits來(lái)對(duì)待的。

所以一個(gè)64bits的操作被分成了兩個(gè)32bits的操作。從而導(dǎo)致了原子性問(wèn)題。

考慮下面的代碼:

public class LongUsage {
    private long i =0;

    public void setLong(long i){
        this.i=i;
    }
    public void printLong(){
        System.out.println("i="+i);
    }
}

因?yàn)閘ong的讀寫是分成兩部分進(jìn)行的,如果在多線程的環(huán)境中多次調(diào)用setLong和printLong的方法,就有可能會(huì)出現(xiàn)問(wèn)題。

解決辦法本簡(jiǎn)單,將long或者double變量定義為volatile即可。

private volatile long i = 0;

以上就是詳解java安全編碼指南之可見性和原子性的詳細(xì)內(nèi)容,更多關(guān)于java安全編碼指南之可見性和原子性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • IDEA插件(BindED)之查看class文件的十六進(jìn)制

    IDEA插件(BindED)之查看class文件的十六進(jìn)制

    這篇文章主要介紹了IDEA插件(BindED)之查看class文件的十六進(jìn)制,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java?接口定義變量的示例代碼

    Java?接口定義變量的示例代碼

    文章介紹了Java接口中的變量和方法,接口中的變量必須是publicstaticfinal的,用于定義常量,而方法默認(rèn)是publicabstract的,必須由實(shí)現(xiàn)類來(lái)實(shí)現(xiàn),接口不能實(shí)例化,只能通過(guò)實(shí)現(xiàn)類來(lái)實(shí)現(xiàn)接口中的方法,本文介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • Java通過(guò)切面實(shí)現(xiàn)統(tǒng)一處理Token設(shè)置用戶信息

    Java通過(guò)切面實(shí)現(xiàn)統(tǒng)一處理Token設(shè)置用戶信息

    這篇文章主要介紹了Java切面統(tǒng)一處理Token設(shè)置用戶信息,常見的后端開發(fā)中,接口請(qǐng)求中一般前端都是先通過(guò)用戶登錄獲取token,每次接口請(qǐng)求都需要在頭信息中攜帶token信息,后端每次都需要手動(dòng)處理token信息,從token信息中解析獲取用戶信息,需要的朋友可以參考下
    2023-10-10
  • java線程池核心線程不被摧毀的原理及分析

    java線程池核心線程不被摧毀的原理及分析

    這篇文章主要介紹了java線程池核心線程不被摧毀的原理及分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • springboot中如何實(shí)現(xiàn)kafa指定offset消費(fèi)

    springboot中如何實(shí)現(xiàn)kafa指定offset消費(fèi)

    這篇文章主要介紹了springboot中如何實(shí)現(xiàn)kafa指定offset消費(fèi),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • IDEA 如何控制編輯左側(cè)的功能圖標(biāo)ICON(操作步驟)

    IDEA 如何控制編輯左側(cè)的功能圖標(biāo)ICON(操作步驟)

    很多朋友被idea左側(cè)的圖標(biāo)不見了這一問(wèn)題搞的焦頭爛額,不知道該怎么操作,今天小編就交大家如何控制編輯左側(cè)的功能圖標(biāo) ICON,文字內(nèi)容不多,主要通過(guò)兩張截圖給大家說(shuō)明,感興趣的朋友一起看看吧
    2021-05-05
  • mybatis連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)雙表查詢

    mybatis連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)雙表查詢

    本文主要介紹了mybatis連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)雙表查詢,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-09-09
  • 工作中Java集合的規(guī)范使用操作詳解

    工作中Java集合的規(guī)范使用操作詳解

    這篇文章主要為大家詳細(xì)介紹了工作中Java集合使用時(shí)需要注意的規(guī)范,文中的示例代碼講解詳細(xì),對(duì)我們使用Java集合有一定的幫助,感興趣的可以了解一下
    2022-11-11
  • Java獲取控制臺(tái)輸入的兩種方法小結(jié)

    Java獲取控制臺(tái)輸入的兩種方法小結(jié)

    這篇文章主要介紹了Java獲取控制臺(tái)輸入的兩種方法小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • java實(shí)現(xiàn)圖片用Excel畫出來(lái)

    java實(shí)現(xiàn)圖片用Excel畫出來(lái)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖片用Excel畫出來(lái),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03

最新評(píng)論

喀什市| 和田市| 公主岭市| 遂宁市| 凯里市| 巫溪县| 万州区| 韶关市| 合山市| 胶南市| 穆棱市| 鲜城| 西和县| 卓尼县| 唐河县| 连江县| 焉耆| 宣化县| 天峻县| 白水县| 吉安县| 尉犁县| 通许县| 黄浦区| 天津市| 长沙市| 金平| 谷城县| 健康| 慈溪市| 武胜县| 唐山市| 溧水县| 鲁甸县| 辛集市| 丰城市| 资中县| 乌苏市| 大同县| 朝阳市| 长乐市|