這篇文章,必須了解Java虛擬機的內存模型和CUP的內存架構扇丛,不了解的同學速度學習前兩篇。
Java內存模型:
Android線程篇(五):Java內存模型
CPU內存架構:
Android線程篇(六):CPU內存架構
繼續(xù)上篇文章的例子,稍作改動:
Int count=0
count=count+1
如果count=count+1
在單線程里面運行尉辑,這個是沒有任何問題的帆精,但是在多線程中運行就會有問題,會有什么問題呢?
當線程執(zhí)行count=count+1
時會先從主內存讀取count
的值隧魄,然后復制一份到CPU的高速緩存中卓练,對count進行+1操作,將count的結果寫入高速緩存中购啄,再將i的值刷新到主內存當中襟企。
如果有倆個線程同時執(zhí)行這個代碼,我們期望的結果為2狮含,到底會出現(xiàn)什么情況呢顽悼?我們繼續(xù)分析。
開始時几迄,倆個線程分別讀取count的值到各自的CPU高速緩存當中蔚龙,線程1和線程2對count進行+1操作,線程1將count的結果寫入高速緩存中映胁,再將i的值刷新到主內存當中木羹,此時線程2高速緩存中,count的值還是0屿愚,進行加1操作之后汇跨,count的值為1务荆,然后線程2把count的值寫入內存,這個時候count的值還為1穷遂。
最終結果i的值是1函匕,而不是2。這就是著名的緩存一致性問題蚪黑。通常稱這種被多個線程訪問的變量為共享變量盅惜。
在多線程編程的時候,如果一個變量在多個CUP中都有緩存忌穿,就可能會出現(xiàn)緩存不一致性問題抒寂。
問題清楚了,我們如何來解決這個問題呢掠剑?
來先上個例子屈芜,至于為什么不用Int請看上篇文章:
文章鏈接:
Java線程并發(fā)小例子的思考,尋求大佬答疑解惑
public Integer count = 0;
public int TestVolatile(){
final CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
count++;
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("<<<<<"+count);
return count;
}
Log:
03-18 03:00:16.098 5569-5569/com.example.myapplication I/System.out: <<<<<863
03-18 03:01:55.414 5569-5569/com.example.myapplication I/System.out: <<<<<1000
03-18 03:01:58.210 5569-5569/com.example.myapplication I/System.out: <<<<<976
03-18 03:02:00.426 5569-5569/com.example.myapplication I/System.out: <<<<<925
我們期望count結果等于1000朴译,結果看log井佑,都是小于1000的,那么我們如何能讓結果等于我們期望的1000呢:
第一種:
采用synchronized:
public Integer count = 0;
public Integer TestVolatile() {
final CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
increase();
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("<<<<<" + count);
return count;
}
public synchronized void increase() {
count++;
}
Log:
03-25 14:20:02.426 15465-15465/? I/System.out: <<<<<1000
03-25 14:20:09.868 15465-15465/com.example.myapplication I/System.out: <<<<<1000
03-25 14:20:13.214 15465-15465/com.example.myapplication I/System.out: <<<<<1000
第二種:采用Lock:
public Integer count = 0;
Lock lock = new ReentrantLock();
public Integer TestVolatile() {
final CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
increase();
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("<<<<<" + count);
return count;
}
public void increase() {
lock.lock();
try {
count++;
} finally{
lock.unlock();
}
}
這里就不貼Log了眠寿,有興趣的朋友自己試躬翁。。盯拱。
第三種:采用AtomicInteger:
public AtomicInteger count = new AtomicInteger();
public AtomicInteger TestVolatile() {
final CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
increase();
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("<<<<<" + count);
return count;
}
public void increase() {
count.getAndIncrement();
}
至于這幾種方法都有什么優(yōu)劣盒发,他們的原理都有什么,后面的文章我們繼續(xù)講解狡逢。