zoukankan      html  css  js  c++  java
  • (一)juc线程高级特性——volatile / CAS算法 / ConcurrentHashMap

    1. volatile 关键字与内存可见性

           原文地址: https://www.cnblogs.com/zjfjava/category/979088.html

      内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。

      可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。

    定义线程类ThreadDemo,功能是将boolean型变量flag的值修改

    复制代码
    class ThreadDemo implements Runnable {
    
        private volatile boolean flag = false;
    
        @Override
        public void run() {
            
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
    
            flag = true;
            
            System.out.println("flag=" + isFlag());
    
        }
    
        public boolean isFlag() {
            return flag;
        }
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    
    }
    复制代码

    测试:线程1修改ThreadDemo类中 flag的值(由false修改为true),线程main方法判断 flag的值,当为true时输出信息,并停止

    复制代码
    public class TestVolatile {
        //线程main方法判断boolean值,当为true时输出“------------” 并停止线程
        public static void main(String[] args) {
            ThreadDemo td = new ThreadDemo();
            //线程1为ThreadDemo的run方法,将boolean值由false改为true
            new Thread(td).start();
            
            while(true){
                    if(td.isFlag()){
                        System.out.println("------------------");
                        break;
                    }
            }    
        }
    }
    复制代码

    运行如下:

    即由于可见性错误导致线程main方法读取到主存中的flag值并不是线程1修改后的值(读取操作发生在线程1将flag值写入主存之前)。

    解决方法:

    (1)使用同步锁synchronized,让线程main重复的到主存中读取数据,但是当多线程操作时,效率很低

    synchronized (td) {
         if(td.isFlag()){
              System.out.println("------------------");
                break;
           } }                   

    (2)使用volatile 变量,用来确保将变量的更新操作通知到其他线程

    private volatile boolean flag = false;

    可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:

    • 对于多线程,不是一种互斥关系
    • 不能保证变量状态的“原子性操作”

    2. 原子变量与 CAS 算法

    以 i++为例,i++操作实际上分为三个步骤

    复制代码
    //i++ 的原子性问题:
       int i = 10;
       i = i++; //10
    //i++ 的操作实际上分为三个步骤“读-改-写”
       int temp = i;
       i = i + 1;
       i = temp;
    复制代码

    测试如下:

    复制代码
    public class TestAtomicDemo {
        public static void main(String[] args) {
            AtomicDemo ad = new AtomicDemo();
        //创建10个线程对serialNumber值进行增加操作        
            for (int i = 0; i < 10; i++) {
                new Thread(ad).start();
            }
        }    
    }
    
    class AtomicDemo implements Runnable{
        //声明volatile变量
        private volatile int serialNumber = 0;
       
        @Override
        public void run() {       
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }        
            System.out.println(getSerialNumber());
        }
        //对变量进行i++操作
        public int getSerialNumber(){
            return serialNumber++;
        }        
    }
    复制代码

    可能会出现如下结果:

    原因为 volatile只能保证内存可见性,但是线程1和线程2中进行的 i++操作却分好几个步骤,即不能保证变量状态的“原子性操作”。

       

    解决方法:

    使用原子变量(在 java.util.concurrent.atomic 包下)

    复制代码
    //    private volatile int serialNumber = 0;
    private AtomicInteger serialNumber = new AtomicInteger(0);
    
    public int getSerialNumber(){
    //      return serialNumber++;
            return serialNumber.getAndIncrement();
        }
    复制代码

    以 AtomicInteger分析原子变量的实现

    (1)volatile 保证内存可见性

      

    (2)CAS(Compare-And-Swap) 算法保证数据变量的原子性

      

    CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。

    CAS 是一种无锁的非阻塞算法的实现。

    CAS 包含了 3 个操作值: 

    • 需要读写的内存值 V
    • 进行比较的值 A
    • 拟写入的新值 B

    当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。

     可通过源码查看sun.misc.Unsafe.class中的getAndAddInt方法实现来理解CAS算法

    复制代码
    public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
       {
         int i;
    do
         {
           i = getIntVolatile(paramObject, paramLong);
         } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
         return i;
       }
    复制代码

    3. 模拟CAS算法

    复制代码
    /*
     * 模拟 CAS 算法
     */
    public class TestCompareAndSwap {
        public static void main(String[] args) {
            final CompareAndSwap cas = new CompareAndSwap();      
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {            
                    @Override
                    public void run() {
                        int expectedValue = cas.get();
                        boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
                        System.out.println(b);
                    }
                }).start();
            }        
        }    
    }
    
    class CompareAndSwap{
        private int value;    
        //获取内存值
        public synchronized int get(){
            return value;
        }    
        //比较
        public synchronized int compareAndSwap(int expectedValue, int newValue){
            int oldValue = value;      
            if(oldValue == expectedValue){
                this.value = newValue;
            }      
            return oldValue;
        }    
        //设置
        public synchronized boolean compareAndSet(int expectedValue, int newValue){
            return expectedValue == compareAndSwap(expectedValue, newValue);
        }
    }
    复制代码

    4. 同步容器类 ConcurrentHashMap

    Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。

    ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分段”机制(可理解为“并行”)替代 Hashtable 的独占锁(相当于“串行”),进而提高性能。

    注意:jdk1.8之后ConcurrentHashMap底层采用的 CAS算法 取代“锁分段”机制。

    此包还提供了设计用于多线程上下文中的 Collection 实现:

    ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。

    • 当期望许多线程访问一个给定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。
    • 当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。
    复制代码
    /*
     * CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
     * 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。
     */
    public class TestCopyOnWriteArrayList {
        public static void main(String[] args) {
            HelloThread ht = new HelloThread();        
            for (int i = 0; i < 10; i++) {
                new Thread(ht).start();
            }
        }    
    }
    
    class HelloThread implements Runnable{
        
    // 使用ArrayList,报错java.util.ConcurrentModificationException
    //    private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
        
        private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();    
        static{
            list.add("AA");
            list.add("BB");
            list.add("CC");
        }
    
        @Override
        public void run() {       
            Iterator<String> it = list.iterator();        
            while(it.hasNext()){
                //迭代和add方法操作同一个数据源
                System.out.println(it.next());
                list.add("AA");
            }        
        }    
    }
    复制代码
  • 相关阅读:
    iOS resign code with App Store profile and post to AppStore
    HTTPS科普扫盲帖 对称加密 非对称加密
    appid 评价
    使用Carthage安装第三方Swift库
    AngularJS:何时应该使用Directive、Controller、Service?
    xcode7 The operation couldn't be completed.
    cocoapods pod install 安装报错 is not used in any concrete target
    xcode7 NSAppTransportSecurity
    learning uboot how to set ddr parameter in qca4531 cpu
    learning uboot enable protect console
  • 原文地址:https://www.cnblogs.com/cxxjohnson/p/10432875.html
Copyright © 2011-2022 走看看