Volatile 關鍵字
當多個線程操作共享數據時,可保存線程內存之間數據可見,還可防止指令重排序。相對於synchronized 是一種更爲輕量級的同步策略。
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
if(td.isFlag()){
System.out.println("----hello world----");
break;
}
}
}
}
class ThreadDemo implements Runnable {
// 問題變量
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
當flag不用Volatile關鍵字修飾輸出:
flag=true
當flag使用Volatile關鍵字修飾輸出:
----hello world----
flag=true
上面代碼可以看出,如果有多條線程同時操作同享數據,如果不使用Volatile關鍵字修飾,就可能出現內存可見性問題。
注意:
- 1、volatile 不具備“互斥性”,不像synchronized關鍵字:多線程情況下當其中一條線程佔據CPU資源之後,其它線程需要阻塞。
- 2、volatile 不保證變量的“原子性”。
原子性問題
原子性問題,例如:i++
int i = 10;
i = i++;
System.out.println(i);
輸出結果:10
上述代碼實際的底層實現:
int temp = i;
i = i + 1;
i = temp
Volatile 關鍵字不保證“原子性”測試
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
// 這裏變量使用Volatile關鍵字修飾
private volatile int serialNumber = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(getSerialNumber());
}
public int getSerialNumber(){
return serialNumber++;
}
}
輸出結果:(兩個相同的結果)
0
8
2
7
6
5
3
1
1
4
由上述結果
可以看到,即使變量使用Volatile關鍵字修飾之後,多線程環境下仍然有出現問題的可能性。
如何解決原子性問題
原子變量:JDK1.5以後 java.util.concurrent.atomic 包下提供有原子變量。
- 1、使用Volatile關鍵字保證變量內存可見性
2、使用CAS(Compare-And-Swap)算法保證變量原子性。CAS算法是硬件對於併發操作共享數據的支持。
CAS 包含三個操作數:- 內存值 V
- 預估值 A
- 更新值 B
當且僅當 V == A 時,V = B。否則,則不做任何操作。
public class Test {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
// 使用原子變量包裝類操作
private AtomicInteger serialNumber = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(getSerialNumber());
}
public int getSerialNumber(){
// 自增
return serialNumber.getAndIncrement();
}
}
輸出結果:(無論多少次操作結果都不會出現有相同值的情況)
6
9
0
2
3
5
4
7
1
8
模擬CAS算法:
public class TestCompareAndSwap {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 設置之前先獲取一下內存值
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap{
// 內存值
private int value;
/**
* 獲取內存值
* @return
*/
public synchronized int get(){
return value;
}
/**
* 比較
* @param expectedValue 預估值
* @param newValue 新值
* @return
*/
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
// 內存值和預估值比較
if(oldValue == expectedValue){
// 如果內存值和預估值相同,就把新值賦值給內存值
this.value = newValue;
}
// 返回舊內存值
return oldValue;
}
/**
* 設置新值
* @param expectedValue 預估值
* @param newValue 新值
* @return 設置結果
*/
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
輸出結果:(有成功有失敗)
true
false
true
false
false
true
false
false
true
false