Java并发中原子操作的实现

来源:互联网 发布:java工程师优秀简历 编辑:程序博客网 时间:2024/04/28 05:44

处理器实现原子操作

如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,所以要保证操作是原子性,则必须保证同一时刻只有一个处理器访问共享变量的内存地址

  1. 使用总线锁定保证原子性

    处理器在总线上输出一个LOCK#信号,那么其他处理器的请求将被阻塞,此时该处理器就可以独占共享内存,从而保证只有一个处理器操作共享内存。

  2. 使用缓存锁定保证原子性
    总线锁把CPU和内存之间通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,现在多用缓存锁来代替总线锁优化。如果缓存在处理器缓存行中的内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,其他处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期,如果过期,就会将该缓存行设置成无效状态,从而避免多个处理器同时修改该共享内存。

JAVA如何实现原子操作

1.使用循环CAS实现原子操作

/**     * 使用CAS实现线程安全计数器     */    private void safeCount() {        for (;;) {            int i = atomicI.get();            boolean suc = atomicI.compareAndSet(i, ++i);            if (suc) {                break;            }        }    }    /**     * 非线程安全计数器     */    private void count() {        i++;    }

可能会遇到的问题:

ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

2.使用锁机制实现原子操作

锁机制保证了只有获得锁的线程能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁,轻量级锁和互斥锁,除了偏向锁,JVM实现锁的方式都用到的循环CAS,当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。

0 0
原创粉丝点击