久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

詳解java安全編碼指南之可見(jiàn)性和原子性

瀏覽:50日期:2022-08-11 11:50:06
目錄不可變對(duì)象的可見(jiàn)性保證共享變量的復(fù)合操作的原子性保證多個(gè)Atomic原子類(lèi)操作的原子性保證方法調(diào)用鏈的原子性讀寫(xiě)64bits的值不可變對(duì)象的可見(jiàn)性

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

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

先來(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)來(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è)類(lèi)本身是不可變的,但是我們對(duì)該對(duì)象的引用refObject是可變的。這就意味著我們可以調(diào)用多次setImmutableObject方法。

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

上面的例子中,在多線程環(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)單的解決可見(jiàn)性的辦法就是加上volatile關(guān)鍵字,volatile關(guān)鍵字可以使用java內(nèi)存模型的happens-before規(guī)則,從而保證volatile的變量修改對(duì)所有線程可見(jiàn)。

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)來(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的值,并寫(xiě)入CPU寄存器中。

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

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

如果在單線程環(huán)境中,是沒(méi)有問(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原子類(lèi):

private AtomicInteger atomicInteger=new AtomicInteger(0);public int increaseWithAtomic(){ return atomicInteger.incrementAndGet();}保證多個(gè)Atomic原子類(lèi)操作的原子性

如果一個(gè)方法使用了多個(gè)原子類(lèi)的操作,雖然單個(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ù)組。

讀寫(xiě)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的讀寫(xiě)是分成兩部分進(jìn)行的,如果在多線程的環(huán)境中多次調(diào)用setLong和printLong的方法,就有可能會(huì)出現(xiàn)問(wèn)題。

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

private volatile long i = 0;

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

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 国产一区二区三区在线 | 免费av播放 | 国产精品国产精品国产专区不片 | 玖玖在线精品 | 91 在线 | 亚洲一区不卡在线 | va在线观看 | 国产精品夜夜春夜夜爽久久电影 | 看亚洲a级一级毛片 | 欧美性网| 日日人人| 精久久久 | 欧美中文在线 | 日韩精品免费在线观看 | 久久久精彩视频 | 日本福利网站 | 国产日韩欧美精品一区二区 | 亚洲一区二区三区在线播放 | 99精品全国免费观看视频软件 | v片网站 | 亚洲久悠悠色悠在线播放 | 久久一区 | 97国产精品久久久 | 国产在线小视频 | 久久免费精品 | 亚洲一区二区三区 | 中文字幕在线一区二区三区 | 国产精品一区二区av | 中文字幕日韩欧美 | 视频在线亚洲 | 精品乱子伦一区二区三区 | 日精品| 成人欧美一区二区三区在线观看 | 国产精品久久久久久久久久大牛 | 少妇久久久 | 国产羞羞视频在线观看 | 亚洲精品视频区 | 亚洲高清一区二区三区 | 一二三精品区 | 精品国产乱码一区二区三区a | 另类综合在线 |