一:final的處理
1.1 經final修飾的變量或者對象,在其構造函數中初始化之後,其他線程一定可以獲得正確的構造版本,即可以獲得變量或者對象字段的最新值。
看下面的代碼:
public class ThreadFinal1 {
public final int i;
public int j;
public ThreadFinal1 final1;
public ThreadFinal1() {
this.i = 2; //一定可以得到正確的構造版本
this.j = 3; //不一定得到正確的構造版本,可能得到默認值0
}
public void write() {
final1 = new ThreadFinal1();
}
public void reader() {
if (final1 != null) {
System.out.println(final1.i);
System.out.println(final1.j);
}
}
}
上述代碼,定義了一個final修飾的i,和一個沒用final修飾的j,在構造函數中賦值後,i在其他線程中一定可以獲得正確的構造版本,即i=2,而j不一定可以獲得正確的構造版本,可能獲得j的默認值0。
1.2 經final修飾的變量或者對象,在其構造函數中初始化之後,其他線程一定可以獲得正確的構造版本,而且字段若重新賦值與final修飾的字段,那麼該字段也一定可以獲得正確的構造版本。
public class ThreadFinal2 {
public final int i;
public int j;
public ThreadFinal1 final1;
public ThreadFinal2() {
this.i = 2; //一定可以得到正確的構造版本
this.j = this.i; //也一定可以得到正確的構造版本
}
public void write() {
final1 = new ThreadFinal1();
}
public void reader() {
if (final1 != null) {
System.out.println(final1.i);
System.out.println(final1.j);
}
}
}
上述代碼中,j賦值爲i,i是final修飾的字段,那麼j在其他線程中也一定可以獲得正確的構造版本,即j=2。
二:字節分裂(word tearing)
在有些處理器中,不能對單個的字節進行修改,這樣就會造成一些問題;如字節數組,多個線程想要去修改它,只能通過把該字節數組copy過來修改,然後再把主內存中的字節數組覆蓋掉,這樣會造成只能有一個線程是修改成功的。
在上圖中,線程1和線程2去修改字節數組,分別修改不同位置的值,這樣就會造成最後只有一個線程修改成功。所以,在多線程編程中,儘量不要去對byte[]數組修改。
三:double和long的特殊處理
double和long都是64位字節,他們的特殊在於,要去修改值,是分成兩部分進行的,即分成兩個32位字節的,這樣就會導致,出現髒數據,如一個線程修改了值,另一個線程讀取,可能讀到的是隻修改一部分的值。
所以在多線程編程中,可以用volidate字段去修飾double和long,並不是說volidate修飾的字段是原子性的,只是在double和long中,用volidate修改,可以保證其原子性。