Java 裏面解決併發問題的有:加鎖(SynChronized,lock),利用管道進行線程間通信, 阻塞隊列(BlockingQueue),使用Java大牛Doug Lea寫的Executors/Executor/ExecutorService/ThreadPoolExecutor,使用計數器CountDownLatch來控制
介紹volatile
在Java線程併發處理中,Java引入了Volatile變量。這次主要說說這個變量
在jdk1.2之前 Java內存模型的實現是總主內存(即共享內存)讀取變量,不需要特別注意,但是隨着jvm的發展和成熟,當前內存模型下,線程是將變量存在本地內存中(比如機器的寄存器),而不是直接從主存中進行讀寫,而這就造成了主存和寄存器的數據不統一。
要解決這個問題,Java給出了Volatile變量,用Volatitle修飾的變量,在每次線程訪問時,都強迫從共享內存中讀取數據成員變量,當成員變量發生變化時,強迫線程變化值寫到共享內存中,這樣,在任何時刻,兩個不同線程總是看到弄個成員變量的同一個值。
Volatile 是一宗削弱的同步機制,在訪問Volatile 修飾的變量時候不會執行加鎖操作,也就不會執行線程阻塞,因此volatilei變量是一種比synchronized關鍵字更輕量級的同步機制。
使用
Java內存包含以下三種情況
- 原子性
- 有序性
- 可見性
Volatile兩層語義
- 保證不同線程對這個變量進行的操作的可見性,即一個線程修改某個變量的值,這新值對其他線程是立即可見的
- 禁止進行指令重排
那麼volatile能保證原子性嗎?
下面的例子
public class VolatileTest {
public static void main(String args[]) {
for (int i = 0; i < 10000; i++) {
new Thread(new VoRunnable()).start();
}
}
static class VoRunnable implements Runnable {
static volatile int ic = 0;
@Override
public void run() {
ic++;
System.out.println("ic = "+ic);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運行結果:有一點概率,但是如果把線程開的越多,則結果會越明顯,最後的值不一定能加到10000,所以表明Volatile是不能保證原子性的。
原理:自增操作不是原子性的,而且Volatile也無法保證對變量的操作都是原子性的。
如要保證原子性可以使用synchronized、lock、AtomicInteger等。
那麼volatile能保證有序性嗎?
例子
public class VolatileTest {
static String sbu = null;
static volatile boolean inited = true;
public static void main(String args[]) {
for (int i = 0; i < 101; i++) {
new Thread(new VoRunnable2(i)).start();
}
new Thread(new VoRunnable()).start();
}
static class VoRunnable2 implements Runnable{
volatile int ic = 0;
VoRunnable2(int i){
this.ic = i;
}
@Override
public void run() {
System.out.println("ic = "+ ic);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ic == 100){
sbu = new String("123456");
inited = false;
}
}
}
static class VoRunnable implements Runnable {
static volatile int ic = 0;
@Override
public void run() {
while (inited){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(sbu == null){
System.out.println(" sbu = kong" );
}else {
System.out.println(" sbu = " + sbu);
}
}
}
}
上述程序運行結果表明:只要Volatile對inited,進行修飾,就不會出現這種問題。
總結
- 它確保指令重排序時不會把其後面的指令排到內存 屏障之前的位置,也不會把前面的指令排到內存屏障的後面; 即在執行到內存屏障這句指令時,在它前面的操作已經全部 完成
- 它會強制將對緩存的修改操作立即寫入主存
- 如果是寫操作,它會導致其他 CPU 中對應的緩存行 無效。