并发问题
在编程的时候我们经常会碰到并发的问题,如果处理不好很有可能造成业务数据的错误。我们思考,到底什么是并发问题?
简单的来说我们可以把并发问题归纳为:未写入而先读取 带来的问题。
我们用最简单的取钱的模型来描述这个问题:
在①②③④ 这个几个步骤中,①②和③④分别是两个独立的过程,如果执行的顺序是 ①③②④,这样就会带来最终余额为负的现象,这个就是一个简单的并发问题。
我们可以用代码简单的模拟这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class AppTest { private int count = 0; public static void main(String[] args) { long ts = System.currentTimeMillis(); AppTest app = new AppTest(); List<Thread> tList = new ArrayList<>(500); for (int i = 0; i < 10000; i++) { Thread thread = new Thread(() -> { for (int m = 0; m < 1000; m++) { app.count(); } }); tList.add(thread); } for (Thread t : tList) { t.start(); } for (Thread t : tList) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(app.count); long tend = System.currentTimeMillis(); System.out.println(tend-ts); } private void count() { count++; } }
|
上述代码执行结果: 9997990(结果具有不确定性,此次结果就偶然一次结果),耗时:3378
造成这个结果的原因就是,在多线程执行的过程中,count的值还没有来得及写入内存,另一个线程就已经把count的读取,就导致count少一次count++运算。
解决并发
既然我们已经知道并发问题,如何解决? 对于并发的解决思路是:保证读取的时候,写入已经完成。具体方法有两种,分别是锁和CAS操作。
使用锁 synchronized
修改count()方法:
1 2 3 4 5 6
| private synchronized void count() { count++; }
|
运行结果:10000000 耗时:4176
锁的方法导致性能下降很多。
使用CAS操作
把int类型的count换成AtomicInteger类型
1 2 3 4 5 6 7
| private AtomicInteger count = new AtomicInteger();
private void count() { count.addAndGet(1); }
|
修改上述两出代码得到运行结果:10000000 耗时:3305