上一篇 <<<Volatile解決JMM的可見性問題
下一篇 >>>CAS無鎖模式及ABA問題
Volatile的偽共享問題
CPU每次均會以固定長度讀取源请,一般為64bit蚓炬,導致就算只改了A扁凛,也會把其他沒改的B-K一起讀取隆嗅,降低了效率
Volatile的偽共享解決辦法
/**
* 解決辦法:【數(shù)據(jù)填充】
* JDK6中,定義p1-6儡率,加上value,一共占用56個字節(jié) 以清,在加上VolatileLong類中頭占用8個字節(jié)一共就是占用64個字節(jié)儿普。
* public final static class VolatileLong{
* public volatile long value = 0L;
* public long p1, p2, p3, p4, p5, p6;
* }
* jdk7中,寫個類單獨繼承方
* public final static class VolatileLong extends AbstractPaddingObject {
* public volatile long value = 0L;
* }
* public class AbstractPaddingObject {
* public long p1, p2, p3, p4, p5, p6;
* }
* jdk8中掷倔,使用注解@sun.misc.Contended,啟動的時候需要加上該參數(shù)-XX:-RestrictContended
* ConcurrentHashMap中就使用了此注解
* @sun.misc.Contended static final class CounterCell {}
*
*/
public class FalseShareTest implements Runnable {
// 定義4和線程
public static int NUM_THREADS = 4;
// 遞增+1
public final static long ITERATIONS = 500L * 1000L * 1000L;
private final int arrayIndex;
// 定義一個 VolatileLong數(shù)組
private static VolatileLong[] longs;
// 計算時間
public static long SUM_TIME = 0l;
public FalseShareTest(final int arrayIndex) {
this.arrayIndex = arrayIndex;
}
public static void main(final String[] args) throws Exception {
for (int j = 0; j < 10; j++) {
System.out.println(j);
if (args.length == 1) {
NUM_THREADS = Integer.parseInt(args[0]);
}
longs = new VolatileLong[NUM_THREADS];
for (int i = 0; i < longs.length; i++) {
longs[i] = new VolatileLong();
}
final long start = System.nanoTime();
runTest();
final long end = System.nanoTime();
SUM_TIME += end - start;
}
System.out.println("平均耗時:" + SUM_TIME / 10);
}
private static void runTest() throws InterruptedException {
Thread[] threads = new Thread[NUM_THREADS];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new FalseShareTest(i));
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
}
public void run() {
long i = ITERATIONS + 1;
while (0 != --i) {
longs[arrayIndex].value = i;
}
}
@sun.misc.Contended
public final static class VolatileLong {
// extends AbstractPaddingObject
// 8個字節(jié) 對象占用8個字節(jié)
public volatile long value = 0L;
// public long p1, p2, p3, p4, p5, p6;
// 48+ 64
}
}
Volatile解決重排序問題
- 重排序:編譯器和處理器為了提高并行的效率會對代碼執(zhí)行重排序眉孩,單線程程序執(zhí)行結(jié)果不會發(fā)生改變的,也就是as-ifserial語義勒葱,但在多線程情況下就會存在問題浪汪。
/**
* thread1和thread2在單線程情況下重排序都沒問題,但在多線程下就存在重排序的問題:
* 第856750次(0,1)
* 第856751次(0,1)
* 第856752次(0,0)
* 解決辦法:使用volatile或手動插入屏障
* UnSafeUtils.getUnsafe().loadFence()--讀屏障
* UnSafeUtils.getUnsafe().storeFence();--寫屏障
*
*
**/
public class ReorderThread {
// 全局共享的變量
private /*volatile*/ static int a = 0, b = 0;
private /*volatile*/ static int x = 0, y = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
//插入一個內(nèi)存寫屏障
UnSafeUtils.getUnsafe().storeFence();
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
//插入一個內(nèi)存寫屏障
UnSafeUtils.getUnsafe().storeFence();
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 && y == 0) {
break;
}
}
}
}
內(nèi)存屏障解決重排序
1.寫內(nèi)存屏障:在指令后插入Stroe Barrier ,能夠讓寫入緩存中的最新數(shù)據(jù)更新寫入主內(nèi)存中凛虽,讓其他線程可見死遭。
2.讀內(nèi)存屏障:在指令前插入load Barrier ,可以讓告訴緩存中的數(shù)據(jù)失效凯旋,強制
讀取主內(nèi)存呀潭,讓cpu緩存與主內(nèi)存保持一致,避免緩存導致的一致性問題至非。
雙重檢驗鎖的單例也應該加上volatile
/**
* 雙重檢驗鎖的單例也應該加上volatile
*
* new Singleton03()完成的動作:
* 1.分配對象的內(nèi)存空間memory=allocate();
* 2.調(diào)用構(gòu)造函數(shù)初始化
* 3.將對象復制給變量
*
* 第二步和第三步流程存在重排序钠署,將對象復制給變量,在執(zhí)行調(diào)用構(gòu)造函數(shù)初始化荒椭,導致另外一個線程獲取到該對象不為空谐鼎,但是該改造函數(shù)沒有初始化,所以就報錯了
*
*/
public class Singleton03 {
private static volatile Singleton03 singleton03;
public static Singleton03 getInstance() {
// 第一次檢查
if (singleton03 == null) {
//第二次檢查
synchronized (Singleton03.class) {
if (singleton03 == null) {
singleton03 = new Singleton03();
}
}
}
return singleton03;
}
public static void main(String[] args) {
Singleton03 instance1 = Singleton03.getInstance();
Singleton03 instance2 = Singleton03.getInstance();
System.out.println(instance1==instance2);
}
}
Volatile和synchronized區(qū)別趣惠?
a.Volatile保證線程可見性狸棍,當工作內(nèi)存中副本數(shù)據(jù)無效之后,主動讀取主內(nèi)存中數(shù)據(jù)信卡,但是并不能保證原子性隔缀,Synchronized是保證線程的原子性
b.Volatile可以禁止重排序的問題,底層使用內(nèi)存屏障。
c.Volatile不會導致線程阻塞,不能夠保證線程安全問題傍菇,synchronized 會導致線程阻塞能夠保證線程安全問題猾瘸,執(zhí)行效率較低。
總體而言volatile關鍵字在某些情況下性能要優(yōu)于synchronized
相關文章鏈接:
<<<多線程基礎
<<<線程安全與解決方案
<<<鎖的深入化
<<<鎖的優(yōu)化
<<<Java內(nèi)存模型(JMM)
<<<Volatile解決JMM的可見性問題
<<<CAS無鎖模式及ABA問題
<<<Synchronized鎖
<<<Lock鎖
<<<AQS同步器
<<<Condition
<<<CountDownLatch同步計數(shù)器
<<<Semaphore信號量
<<<CyclicBarrier屏障
<<<線程池
<<<并發(fā)隊列
<<<Callable與Future模式
<<<Fork/Join框架
<<<Threadlocal
<<<Disruptor框架
<<<如何優(yōu)化多線程總結(jié)