java多线程的原子性与可见性以及线程安全的AtomicLong使用

 1.原子性

Java中的原子性,是指:原子操作是不能被线程调度机制中断的;操作一旦开始,它一定会在可能发生的“上下文切换”(即切换到其他线程执行)之前执行完毕
但是千万不要认为“原子操作不需要同步控制(这是错误的)”!
原子性可以应用于除long和double之外的基本类型的简单操作(赋值和返回值)。long和double这种64位的数据类型,在JVM内部是通过两条32位的操作完成的,因此有可能发生上下文切换。如果给long和double变量加上volatile关键字也可以获得原子性
但是原子性并不能保证并发的代码的正确性。比如,在多处理器(可能是单处理器但是多核)多线程环境下,一个线程对某一 变量的写入操作有可能只是将这种变化存在了CPU缓存中,而其他线程对该变量的访问只是局限在各自的CPU缓存,这样导致了不一致性。因此,还需要使用 volatile关键字来保证可见性,将变量的修改直接写入到内存中。
当然同步(加锁)机制也可以保证这种可见性,将变量的修改直接写到内存中。
在同一个任务中,可见性问题是不存在的。即该任务对变量的修改,该任务肯定知道。
但是,当一个属性的值依赖于它之前的值时(如递增操作),一个属性的值依赖于其他域的值的限制,volatile就无法工作了。这里说的不能工作了,应该是指在并发环境下无法保证代码的正确性吧。
我的理解:原子性+volatitle可以保证并发的正确性。但是,最好编码时还是尽量用 同步加锁 来保证并发的正确性。第一选择应该是synchronized关键字。
再次声明:在Java中,自增操作不是原子的。
在《Thinking in java》第四版中文版的682-684页举例详细说明下面两点:
(1)在java中,对除long和double之外的基本类型的简单操作(赋值、返回值)是原子性的,但是无法保证并发的正确性。
(2)在java中,递增操作不是原子性的,会引发并发问题。

2.原子类Atomic

在JDK5之后,引入了AtomicInteger、AtomicLong、AtomicReference等原子类。这些类其实是用来构建 java.util.concurrent中的类的。也就是说,这些类是JUC实现的基础类,JUC构建在Atomic之上,JUC的并发是用 Atomic来实现的。
我们在写代码时,要首先考虑使用synchronized关键字和Lock对象,而避免使用Atomic类。当涉及到调优时,可以考虑使用Atomic类。Atomic类要比synchronized、Lock更高效

---------------------------------------------------------------------------------------

看一个计数的类:

    public class Counter {  
        private static long counter = 0;  
        public static long addOne(){  
            return ++counter;  
        }  
    }  

 

初看感觉没啥问题,但这个类在多线程的环境下就会有问题了。

假如开多个线程都来使用这个计数类,它会表现的“不稳定”

    public static void main(String[] args) {  
        for(int i=0;i<100;i++){  
            Thread thread = new Thread(){  
                @Override  
                public void run() {  
                    try {  
                        Thread.sleep(100);  
                        if(Counter.addOne() == 100){  
                            System.out.println("counter = 100");  
                        }  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }  
            };  
            thread.start();  
        }  
    }  

 

程序会开100个线程,每个线程都会把counter 加一。那么应该有一个线程拿到counter =100的值,但实际运行情况是大多数据情况下拿不到100,在少数情况能拿到100.

因为 Counter 类在 addOne()方法被调用时,并不能保证线程的安全,即它不是原子级别的运行性,而是分多个步骤的,打个比方:

线程1首先取到counter 值,比如为10,然后它准备加1,这时候被线程2占用了cpu,它取到counter为10,然后加了1,得到了11。这时线程1 又拿到了CPU资源,继续它的步骤,加1为11,然后也得到了11。这就有问题了。

那么怎么解决它呢?JDK在concurrent包里提供了一些线程安全的基本数据类型的实现,比如 Long型对应的concurrent包的类是AtomicLong。

现在修改下代码:

    import java.util.concurrent.atomic.AtomicLong;  
      
      
    public class Counter {  
        private static AtomicLong counter = new AtomicLong(0);  
      
      
        public static long addOne() {  
            return counter.incrementAndGet();  
        }  
    }  

 

运行了多次,结果都是能输出counter = 100。

所以在多线程环境下,可以简单使用AtomicXXX 使代码变得线程安全。

 

转:

【1】http://blog.csdn.net/fan2012huan/article/details/51713508

【2】http://blog.csdn.net/yaqingwa/article/details/17737771

 

posted @ 2017-08-21 20:48  Allen101  阅读(560)  评论(0)    收藏  举报