victory的博客

长安一片月,万户捣衣声

0%

并发 | volatile、synchronized和原子操作

volatile、synchronized和原子操作

volatile

volatile是轻量级的synchronized,它在多处理器并发中保证了共享变量的“可见性”。可见性是指当一个线程修改一个共享变量时
,另外一个线程能读到这个修改的值(Java内存模型确保所有线程看到这个变量的值是一致的)。

volatile的两条实现原则

(1)Lock前缀指令会引起处理器缓存回写到内存。
(2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效。

volatile的使用优化

使用追加到64字节的方式来填满高速缓冲区的缓存行,避免头节点和尾节点加载到同一个缓存行,使头、尾节点在修改时不会互相锁定。

synchronized实现原理

JVM基于进入和推出Monitor对象来实现方法同步和代码块同步,但两者实现细节不一样。
代码块同步使用monitorenter和monitorexit指令实现的。monitorenter指令在编译后插入到同步代码块的开始位置,而monitorexit是
插入到方法结束处和异常处,JVM要博爱正每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,
当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象monitor的所有权,即尝试获得对象的锁。

原子操作

java如何实现原子操作?
通过循环CAS的方式实现原子操作。JVM中CAS操作使用处理器提供的CMPXCHG指令实现。自旋CAS实现的基本思路就是循环及进行CAS操作指导成功为止。

使用CAS实现线程安全计数器

package concurrency;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger atomicI = new AtomicInteger(0);
    private int i = 0;
    
    public static void main(String[] args) {
        final Counter cas = new Counter();
        List<Thread> ts = new ArrayList<Thread>(600);
        long start = System.currentTimeMillis();
        for(int j = 0; j < 100; j++){
            Thread t = new Thread(new Runnable(){
                @Override
                public void run(){
                    for(int i = 0; i < 10000; i++){
                        cas.count();
                        cas.safeCount();
                    }
                }
            });
            ts.add(t);
        }
        for(Thread t : ts){
            t.start();
        }
        //等待所有线程执行完成
        for(Thread t : ts){
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(cas.i);
        System.out.println(cas.atomicI.get());
        System.out.println(System.currentTimeMillis() - start);
    }
    
    //使用CAS实现线程安全计数器
    private void safeCount(){
        for(;;){
            int i = atomicI.get();
            boolean suc = atomicI.compareAndSet(i, ++i);
            if(suc){
                break;
            }
        }
    }
    
    //非线程安全计数器
    private void count(){
        i++;
    }
}

CAS实现原子操作的三大问题

(1)ABA问题。可以使用版本号解决。JDK Atomic AtomicStampedReference可以解决ABA问题。
(2)循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
(3)只能保证一个共享变量的原子操作。对多个共享变量操作可以使用锁/将多个共享变量合并成一个共享变量来操作(两个共享变量i=2,j=a,合并以下ij=2a,然后采用CAS来操作)。