最近写了一个程序,是采用多线程往redis里面写入数据,想统计一下一共写了多少条数据,于是用了一个static的全局变量count来累加,这块代码抽象出来就是这样的:
1 public class MultiThread implements Runnable { 2 private String name; 3 private static Integer count = 0; 4 5 public MultiThread() { 6 } 7 8 public MultiThread(String name) { 9 this.name = name; 10 } 11 12 public void run() { 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 void main(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 implements Runnable { 2 private String name; 3 private static Integer count = 0; 4 5 public MultiThread() { 6 } 7 8 public MultiThread(String name) { 9 this.name = name; 10 } 11 12 public void run() { 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 void countPlus(){ 28 count++; 29 } 30 31 32 public static void main(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的作用的。