这篇文章,必须了解Java虚拟机的内存模型和CUP的内存架构,不了解的同学速度学习前两篇。
Java内存模型:
Android线程篇(五):Java内存模型
CPU内存架构:
Android线程篇(六):CPU内存架构
继续上篇文章的例子,稍作改动:
Int count=0
count=count+1
如果count=count+1
在单线程里面运行,这个是没有任何问题的,但是在多线程中运行就会有问题,会有什么问题呢?
当线程执行count=count+1
时会先从主内存读取count
的值,然后复制一份到CPU的高速缓存中,对count进行+1操作,将count的结果写入高速缓存中,再将i的值刷新到主内存当中。
如果有俩个线程同时执行这个代码,我们期望的结果为2,到底会出现什么情况呢?我们继续分析。
开始时,俩个线程分别读取count的值到各自的CPU高速缓存当中,线程1和线程2对count进行+1操作,线程1将count的结果写入高速缓存中,再将i的值刷新到主内存当中,此时线程2高速缓存中,count的值还是0,进行加1操作之后,count的值为1,然后线程2把count的值写入内存,这个时候count的值还为1。
最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。
在多线程编程的时候,如果一个变量在多个CUP中都有缓存,就可能会出现缓存不一致性问题。
问题清楚了,我们如何来解决这个问题呢?
来先上个例子,至于为什么不用Int请看上篇文章:
文章链接:
Java线程并发小例子的思考,寻求大佬答疑解惑
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();
}
至于这几种方法都有什么优劣,他们的原理都有什么,后面的文章我们继续讲解。
网友评论