最近写了一个程序,是采用多线程往redis里面写入数据,想统计一下一共写了多少条数据,于是用了一个static的全局变量count来累加,这块代码抽象出来就是这样的:
1 public class MultiThread implementsRunnable {2 privateString name;3 private static Integer count = 0;4
5 publicMultiThread() {6 }7
8 publicMultiThread(String name) {9 this.name =name;10 }11
12 public voidrun() {13 for (int i = 0; i < 5; i++) {14 //模拟写入redis的IO操作消耗时间
15 try{16 Thread.sleep(200);17 } catch(InterruptedException e) {18 e.printStackTrace();19 }20
21 //累加写入次数
22 count++;23 System.out.println(name + "运行 " + i + " 写入条数:" +count);24 }25 }26
27 public static voidmain(String[] args) {28 for (int i = 0; i < 100; i++) {29 new Thread(new MultiThread("Thread"+i)).start();30 }31 }32 }
启动了100个线程,每个线程写入5次,预计结果应该是500,但是实际结果是这样的:
分析了原因,应该是因为count++不是原子操作,这句代码实际上是执行了3步操作:1,获取类变量count值。2,count+1。3,将count+1后的结果赋值给类变量count。在这3步中间都有可能中断执行其他线程。这样比如线程1先获取了count=0,这时候切换到线程2,线程2获取了count=0,然后又切换到线程1,线程1执行count++=1并修改了类变量count=1,之后又切换到线程2,线程2对之前它获取到的count=0执行count++=1并修改类变量count=1。问题出现了,明明有两个线程对count累加了两次,但是由于count没有加锁,最终类变量只加了1。
根据分析修改程序成下面这样,给count加了同步,将上面代码中第22行的"count++"改为了:
1 synchronized(count) {2 count++;3}
这次运行前两次都正常显示了500,但是多运行几次发现个别时候仍然有问题:
再次分析原因,分析不出来了,开始各种修改各种试,终于成功试验出了正确代码,将count++移到外面,封装到类的静态同步方法里:
1 public class MultiThread implementsRunnable {2 privateString name;3 private static Integer count = 0;4
5 publicMultiThread() {6 }7
8 publicMultiThread(String name) {9 this.name =name;10 }11
12 public voidrun() {13 for (int i = 0; i < 5; i++) {14 //模拟写入redis的IO操作消耗时间
15 try{16 Thread.sleep(200);17 } catch(InterruptedException e) {18 e.printStackTrace();19 }20
21 //累加写入次数
22 countPlus();23 System.out.println(name + "运行 " + i + " 写入条数:" +count);24 }25 }26
27 private static synchronized voidcountPlus(){28 count++;29 }30
31
32 public static voidmain(String[] args) {33 for (int i = 0; i < 100; i++) {34 new Thread(new MultiThread("Thread"+i)).start();35 }36 }37 }
这次运行多次结果均是正常的,为了确保结果正确,又把线程数改为1000试验了多次,结果也是正确的(5000,不过要好好找找了,因为countPlus()和sysout在多个线程里会交错执行,这个5000不一定会出现在什么位置...从最后一行往前找吧...)。
看着这个运行结果,基本能猜到原因了,原因就出在这一句:
1 new Thread(new MultiThread("Thread"+i)).start();
这里为每个线程new了一个对象,所以之前的
1 synchronized(count) {2 count++;3 }
的作用范围是同一个对象的多个线程,也就是说它能够确保Thread1对象的多个线程访问count的时候是同步的,而实际上我们是多线程多实例,每个线程都对应一个不同的对象,所以这句代码实际上是不能起到同步count的作用的。