久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

java可見性和原子性舉例分析

156次閱讀
沒有評論

共計 5726 個字符,預(yù)計需要花費 15 分鐘才能閱讀完成。

這篇文章主要講解了“java 可見性和原子性舉例分析”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著丸趣 TV 小編的思路慢慢深入,一起來研究和學習“java 可見性和原子性舉例分析”吧!

簡介

java 類中會定義很多變量,有類變量也有實例變量,這些變量在訪問的過程中,會遇到一些可見性和原子性的問題。這里我們來詳細了解一下怎么避免這些問題。

不可變對象的可見性

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

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

先來討論一下可變性,我們考慮下面的一個例子:

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

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

然后我們定義一個類來 get 和 set 這個 ImmutableObject:

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

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

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

再來討論一下可見性。

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

答案是否定的。

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

怎么解決呢?

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

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

另外,使用鎖機制,也可以達到同樣的效果:

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

最后,我們還可以使用原子類來達到同樣的效果:

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ù)合操作的原子性

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

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

考慮下面的例子:

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

例子中我們對 int i 進行累加操作。但是 ++ 實際上是由三個操作組成的:

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

CPU 寄存器中將 i 值 +1

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

如果在單線程環(huán)境中,是沒有問題的,但是在多線程環(huán)境中,因為不是原子操作,就可能會發(fā)生問題。

解決辦法有很多種,第一種就是使用 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();
 }

保證多個 Atomic 原子類操作的原子性

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

我們看一個例子:

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

上面的例子中,我們定義了兩個 AtomicInteger,并且分別在 update 和 get 操作中對兩個 AtomicInteger 進行操作。

雖然 AtomicInteger 是原子性的,但是兩個不同的 AtomicInteger 合并起來就不是了。在多線程操作的過程中可能會遇到問題。

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

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

如果我們要創(chuàng)建一個對象的實例,而這個對象的實例是通過鏈式調(diào)用來創(chuàng)建的。那么我們需要保證鏈式調(diào)用的原子性。

考慮下面的一個例子:

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

很簡單的一個對象,我們定義了三個屬性,每次 set 都會返回對 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();

因為在多線程環(huán)境下,上面的 set 方法可能會出現(xiàn)混亂的情況。

怎么解決呢?我們可以先創(chuàng)建一個本地的副本,這個副本因為是本地訪問的,所以是線程安全的,最后將副本拷貝給新創(chuàng)建的實例對象。

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

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

因為 lambda 表達式中使用的變量必須是 final 或者 final 等效的,所以我們需要構(gòu)建一個 final 的數(shù)組。

讀寫 64bits 的值

在 java 中,64bits 的 long 和 double 是被當成兩個 32bits 來對待的。

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

考慮下面的代碼:

public class LongUsage {private long i =0;public void setLong(long i){this.i=i;
 }public void printLong(){ System.out.println( i= +i);
 }
}

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

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

private volatile long i = 0;

感謝各位的閱讀,以上就是“java 可見性和原子性舉例分析”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對 java 可見性和原子性舉例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是丸趣 TV,丸趣 TV 小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-08-25發(fā)表,共計5726字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 宁河县| 永城市| 卢氏县| 秀山| 唐河县| 锡林郭勒盟| 湘西| 旬邑县| 麦盖提县| 石楼县| 石阡县| 辉县市| 洞口县| 星子县| 桃园市| 万州区| 交口县| 方城县| 新安县| 墨玉县| 崇明县| 金川县| 赤城县| 黄浦区| 荆州市| 富顺县| 贵南县| 五峰| 金塔县| 乌恰县| 锦屏县| 茌平县| 甘洛县| 涟源市| 会昌县| 会宁县| 承德县| 遂溪县| 慈利县| 怀来县| 武夷山市|