原子性和可见性有序性

原子性:
原子性就是说一个操作不可以被中途cpu暂停然后调度, 即不能被中断, 要不就执行完, 要不就不执行. 如果一个操作是原子性的, 那么在多线程环境下, 就不会出现变量被修改等奇怪的问题.
原子性的操作是线程安全的。非原子性操作是线程不安全的(也就是说只有简单的读取,赋值操作是原子性操作–(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作))
boolean a = true;
if(a){
a=false;
}
其中 读取a的值,和对a赋值的操作都是原子性操作

可见性:

Java提供了volatile关键字来保证可见性。当一个变量被volatile修饰时再他的值被修改之后会立即刷新进主存当中,在其他线程读取这个变量时也需要到主存当中读取。volatile保证一个线程修改的值对其他线程是立即可见的。(另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。)
常见的一段代码:
线程1:
boolean state = false;
while(!state) {
doSth();
}

线程2:
state = true;
线程1先执行,线程2后执行(这是我们经常用来终止一个线程的操作)。但是当线程2执行之后线程1会立即停止吗?由于线程2修改state之后对线程1并不是立即可见的。所以线程1不一定会立即停止(在大多数时候线程1是能够终止的。)因为每个线程都有自己的工作内存线程2修改值之后对线程1并不是立即可见的。所以不一定立即停止。(上面的代码如果给state加上volatile关键字-那么线程1,2中的工作内存中的state是无效的。对state的简单赋值操作是直接修改主存。读操作也是每次从主存中读取。)

有序性:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4

java在运行的过程当中无法保证语句1,和语句2谁先运行,谁后运行。但是可以保证在一个线程里语句1和语句2的先后顺序对最后的执行结果没有影响(但是可以通过依赖保证语语句3一定在语句1之后执行,语句4一定在语句3之后执行)

//线程1:
context = loadContext(); //语句1
inited = true; //语句2

//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
但是在多个线程里就无法保证了。上面代码里很有可能语句2执行在语句1之前。这样的话当context还有初始化,线程2就跳出循环使用context了。这样就不安全了。

这时候可以使用volatile关键字。volatile对 inited进行修饰,就可以保证在执行语句2的时候,语句2上面的代码全部执行完了。且语句2下面的代码一个都没有执行。但是语句2上面和语句2下面代码的顺序不保证。

//x、y为非volatile变量
//flag为volatile变量

x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
  并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。

看一段常见的代码:
public class Test {
public volatile int inc = 0;

public void increase() {
    inc++;
}

public static void main(String[] args) {
    final Test test = new Test();
    for(int i=0;i<10;i++){
        new Thread(){
            public void run() {
                for(int j=0;j<1000;j++)
                    test.increase();
            };
        }.start();
    }

    while(Thread.activeCount()>1)  //保证前面的线程都执行完
        Thread.yield();
    System.out.println(test.inc);
}

}
这段代码每次执行的结果不一样,但是总是小于10000的
(每次读的都是主存中的没错,但是自增操作不是原子性的,是可以被打断的。当一个线程中的增加操作暂停了。另一个线程可能又去主存读了。这时候就可能导致2个自增操作。最后结果只加了1.)
可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。

  这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。

  在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:

  假如某个时刻变量inc的值为10,

  线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

  那么两个线程分别进行了一次自增操作后,inc只增加了1。

  解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。

  根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。

可以修改为下面几种写法:
采用synchronized:
public class Test {
public int inc = 0;

public synchronized void increase() {
    inc++;
}

public static void main(String[] args) {
    final Test test = new Test();
    for(int i=0;i<10;i++){
        new Thread(){
            public void run() {
                for(int j=0;j<1000;j++)
                    test.increase();
            };
        }.start();
    }

    while(Thread.activeCount()>1)  //保证前面的线程都执行完
        Thread.yield();
    System.out.println(test.inc);
}

}

采用Lock:
public class Test {
public int inc = 0;
Lock lock = new ReentrantLock();

public  void increase() {
    lock.lock();
    try {
        inc++;
    } finally{
        lock.unlock();
    }
}

public static void main(String[] args) {
    final Test test = new Test();
    for(int i=0;i<10;i++){
        new Thread(){
            public void run() {
                for(int j=0;j<1000;j++)
                    test.increase();
            };
        }.start();
    }

    while(Thread.activeCount()>1)  //保证前面的线程都执行完
        Thread.yield();
    System.out.println(test.inc);
}

}

  采用AtomicInteger:
  public class Test {
public AtomicInteger inc = new AtomicInteger();

public  void increase() {
    inc.getAndIncrement();
}

public static void main(String[] args) {
    final Test test = new Test();
    for(int i=0;i<10;i++){
        new Thread(){
            public void run() {
                for(int j=0;j<1000;j++)
                    test.increase();
            };
        }.start();
    }

    while(Thread.activeCount()>1)  //保证前面的线程都执行完
        Thread.yield();
    System.out.println(test.inc);
}

}
转:http://www.cnblogs.com/dolphin0520/p/3920373.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值