java如何实现原子操做

处理器如何实现原子操做

使用总线锁保证原子性

总线锁 就是使用处理器提供的一个Lock # 信号,当一个处理器在总线上输出此信号时,其余处理器的请求将被阻塞住,那么该处理器能够独占共享内存。java

使用缓存锁保证原子性

所谓缓存锁定,是指内存区域若是被缓存在处理器的缓存行中,而且在Lock操做期间被锁定,那么当它执行锁操做写会到内存时,处理器不在总线上声言Lock # 信号,而是修改内部的内存地址,并容许它的缓存一致性机制来保证操做的原子性,由于缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存预期数据,当其余处理器写回已被锁定的缓存行的数据时,会使缓存行无效。面试

不能使用缓存锁定的状况缓存

第一种状况是:当操做的数据不能被缓存在处理器内部,或操做的数据跨多个缓存行时,则处理器会调用总线锁定。 第二章状况是:有些处理器不支持缓存锁定。安全

java如何实现原子操做

从Java 1.5 开始,JDK的并发包里提供了一些类来支持原子操做,这些原子包装类还提供了有用的工具方法,好比以原子的方法将当前值自增1和自减1.并发

package com.control;


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


/**
 * Created by cxx on 2018/2/1.
 * java 如何实现原子操做:经过锁和循环CAS的方式来实现原子操做
 */
public class ControlCount {
    //一个基于CAS线程安全的计数器方法safeCount 和一个线程非安全的计数器count
    private AtomicInteger atomicI = new AtomicInteger(0);
    private int i = 0;
    public static void main(String[] args) {
        final ControlCount cas = new ControlCount();
        List<Thread> ts = new ArrayList<Thread>();
        // 添加100个线程
        for (int j = 0; j < 100; j++) {
            ts.add(new Thread(new Runnable() {
                public void run() {
                    // 执行100次计算,预期结果应该是10000
                    for (int i = 0; i < 100; i++) {
                        cas.count();
                        cas.safeCount();
                    }
                }
            }));
        }
        //开始执行
        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());
    }

    /***
     * 使用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问题app

产生的缘由:CAS须要在操做值的时候,检查值有没有发生变化,若是没有发生变化则更新,可是若是一个值原来是A,变成B,又变成了A,那么使用CAS进行检查时会发现他的值没有发生变化,可是实际上确变化了。工具

解决思路:在变量前面追加上版本号,每次变量更新的时候把版本号加1,从Java1.5开始,JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的做用是首先检查当前引用是否等于预期引用,并检查当前标志是否等于预期标致,若是所有相等,则以原子方式将该引用和该标致设置为给定的更新值。atom

2.循环时间长开销大。 自旋CAS若是长时间不成功,会给cpu带来很是大的执行开销。若是JVM能支持处理器提供的pause指令,那么效率会有必定的提高。pause知道有两个做用:第一,他能够延迟流水线执行指令(de-pipeline),使cpu不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;第二,它能够避免在退出循环时因内存顺序冲突(Memory Order Violation) 而引发CPU流水线被清空,从而提供CPU的执行效率。线程

3.只能保证一个共享变量的原子操做。当对一个共享变量执行操做时,咱们可使用循环CAS的方式来保证原则操做,可是对于多个共享变量操做时,循环CAS就没法保证操做的原子性,这个时候就能够用锁。code

总结

原子操做的保证只有 CAS和 锁两种机制,CAS只能使用一个共享变量,多个变量要用锁。

面试问题示例

public class ControlCount {

    private int i;
    @RequestMapping("/request")
    public String downQuestionByCode(HttpServletRequest request, ModelMap model){
        return i++;
    }
}

分析上面代码存在的问题。

相关文章
相关标签/搜索