zoukankan      html  css  js  c++  java
  • 线程安全

      多个线程不管以何种方式访问某个类,并且在调用的代码中不用进行同步,都能够表现正确的行为。那么就说线程是安全的。

    线程安全有以下几种实现方式:

    1.不可变

      不可变(immutable)的对象一定是线程安全的,不需要再采取任何的安全保护措施。只要一个不可变的对象构造出来,它在任何线程中都是一致的。多线程环境下,应尽量使对象成为不可变,来满足线程安全。

    不可变的类型有:

      final修饰的变量

      String

      枚举类型

      Number的部分子类,如LongDouble等数值包装类型,BigIntgerBigDecimal等大数据类型,但是原子类AtomicIntegerAtomicLong则是可变的。

      对于集合类型,可以使用Collections.unmodifiable()方法来获取一个不可变的集合。

    package ConcurrentExemple;
    
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    
    public class ImmutableExemple {
        public static void main(String[]args){
            Map<String,Integer>map=new HashMap<>();
            Map<String,Integer> immutableMap= Collections.unmodifiableMap(map);
            immutableMap.put("map",2);
    
        }
    }
    
    
    Exception in thread "main" java.lang.UnsupportedOperationException
    	at java.base/java.util.Collections$UnmodifiableMap.put(Collections.java:1455)
    	at ConcurrentExemple.ImmutableExemple.main(ImmutableExemple.java:11)
    
    

    2.互斥同步

      synchronizedReentrantLock

    3.非阻塞同步

      互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步

      互斥同步属于一种悲观并发策略,总是认为只要不做正确的同步措施,那就肯定会出现不安全,无论共享数据是否出现竞争,它都会进行加锁

    1.CAS

      随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其他的线程争用数据,那就成功了,否则要采取补偿措施(不断的重试,直到成功)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此称为非阻塞同步

      乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证,只能靠硬件来保证,硬件原子性操作最典型的是:比较并交换(compare-and-swap)。CAS指令需要有三个操作数,分别是内存地址V,旧的预期值A和新值B。当执行操作时,只有当V的值等于A,才将V的值更新为B。

    2.AtomicInteger

      J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。以下代码使用了 AtomicInteger 执行了自增的操作。

    private AtomicInteger cnt=new AtomicInteger;
    public void add(){
        cnt.incrementAndGet();
    }
    
    public final int incrementAndGet(){
        return unsafe.getAndAddInt(this,valueOffset,1)+1
    }
    

      以下代码是 getAndAddInt() 源码,var1 指示对象内存地址var2 指示该字段相对对象内存地址的偏移var4 指示操作需要加的数值,这里为 1。通过 getIntVolatile(var1, var2) 得到旧的预期值,通过调用 compareAndSwapInt() 来进行 CAS 比较,如果该字段内存地址中的值等于 var5,那么就更新内存地址为 var1+var2 的变量为 var5+var4。可以看到 getAndAddInt() 在一个循环中进行,发生冲突的做法是不断的进行重试。

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
        return var5;
    }
    

    3.ABA

      如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效

  • 相关阅读:
    Asp.Net MVC<一> : 三层架构、MVC
    To IOC,代码结构演变的随想
    .net网站的文件上传读取进度条和断点下载
    NPOI导入,导出
    瀑布流,纵向
    主键、外键、索引
    java基础语法要点<二>(基于1.8)
    android 概述 及四大组件
    Android Studio
    C#查看各种变量的指针地址
  • 原文地址:https://www.cnblogs.com/yjxyy/p/11125528.html
Copyright © 2011-2022 走看看