Java多线程之i++的安全性问题

来源:互联网 发布:数据管理软件开发 编辑:程序博客网 时间:2024/05/17 08:12

问题:
两个线程同时对i=0的数据分别进行i++一百次,结果出来并不是200。理论上来讲,结果最小值为2,最大值为200。
首先解释一下为什么会这样。
i++并非原子操作。执行过程中JVM从内存中把变量i的值取出来放到寄存器中,在寄存器中做加1操作,之后把寄存器中的值在写入到内存中。
值为2的情况:
线程A:从内存中读到i值为0,在寄存器中加1,还没有写入内存时线程B开始执行。
线程B:从内存中读到i值依然为0,线程B执行99次i++,这时候写入内存,此时内存中i=99,还没开始第100次读取内存中i的值,线程A开始执行。
线程A:把寄存器中的1写入到内存,此时内存中i=1,还没有开始第二次读取内存中i的值时,线程B开始执行。
线程B:把寄存器中的i=1读入到寄存器,进行加1操作,此时寄存器中值为2,还没有写入到内存中时,线程A开始执行。
线程A:把内存中的i=1读入到寄存器中加1然后写入内存,执行99次后,内存中的i=100,此时线程A执行完毕。线程B开始执行。
线程B:把寄存器中的i=2写入到内存中,线程B结束。
此时内存中的i=2。
值为200的情况:
线程完成从内存中读取到寄存器运算后写入内存这一个完整步骤。
其余情况类似。

解决这个问题的方法是加入同步块。
一开始我很愚蠢的这样写了

Count.java

package com.hnzhrh.Java7MultipleThread;public class Count {    public int i=0;}

CountRunnable.java

package com.hnzhrh.Java7MultipleThread;public class CountRunnable implements Runnable{    Count count=null;    public CountRunnable(Count count) {        // TODO Auto-generated constructor stub        this.count=count;    }    public synchronized void add(){        for(int j=0;j<100;j++){            count.i++;        }    }    @Override    public void run() {        // TODO Auto-generated method stub        add();    }}

Main.java

package com.hnzhrh.Java7MultipleThread;public class Main {    public static void main(String[] args) throws InterruptedException {        Count count=new Count();        Thread a =new Thread(new CountRunnable(count));        Thread b =new Thread(new CountRunnable(count));        a.start();        b.start();        a.join();        b.join();        System.out.println(count.i);    }}

这个输出是错误的!完全错误的!一开始还怀疑这同步块并没有同步啊,后来发现是我傻逼了。

解释这个问题就要看一下JVM的内存模型:
这里写图片描述
JVM把内存分为两块,一块为线程栈,一块为堆。不同线程栈之间是不能相互访问的。比如线程栈A中有个本地变量,那么线程栈B是不能访问到该变量的。当然需要注意引用的情况,如果线程栈A中有个类的变量指向堆中的一个对象,线程栈B中也有个类的变量指向堆中的同一个对象,那么写操作是非线程安全的。
有了这点基础,我们可以分析一下我上面代码错到哪了。
我把add方法用了同步块,但是,我的add方法写在了实现了Runnable接口的CountRunnable类中,那么我创建了两个线程A和B,线程栈A中有这个方法,线程栈B中有这个方法,两个方法互不相干的对堆中的Count对象中的成员变量i进行操作,当然没有达到同步的作用。
只要做以下修改就可以了:
Count.java

package com.hnzhrh.Java7MultipleThread;public class Count {    public int i=0;    public synchronized void add(){        for(int j=0;j<100;j++){            i++;        }    }}

CountRunnable.java

package com.hnzhrh.Java7MultipleThread;public class CountRunnable implements Runnable{    Count count=null;    public CountRunnable(Count count) {        // TODO Auto-generated constructor stub        this.count=count;    }    @Override    public void run() {        // TODO Auto-generated method stub        count.add();    }}
0 0
原创粉丝点击