java concurrent
来源:互联网 发布:夏宽大师 淘宝 编辑:程序博客网 时间:2024/06/08 17:04
Java concurrent
java concurrent包是常用的jdk包之一,主要用于多线程编程,我认为可以主要由以下几部分构成:
- 线程安全数据结构
- 线程管理
- 线程执行
目录
- Java concurrent
- 线程安全数据结构
- CAS
- 线程安全数据结构
线程安全数据结构
所谓线程安全的数据结构,即该数据结构的实例在多线程环境下运行时,可以保证每个线程可以正确的读和写该实例,而不会因为线程运行的时间不确定性导致该数据结构的值不可控。
为了达到这个目的,concurrent包里采用了两类方法实现
- CAS(compare and swap)
- 锁
接下来来看这两类实现方法的原理。
CAS
CAS又叫compare and swap, wiki,concurrent中使用这种方法实现的线程安全类有下面几个:
可以从命名上看出这些类均已Atomic开头,Atomic翻译过来是原子,所以以AtomicInteger为例,可以理解成为这个整形的操作都是原子操作。
如下是AtomicInteger的部分源码,
private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;
这里的value就是我们平时使用Integer的value。这个变量一定需要声明成volatile的,原因是保证这个变量的值随时可被所有线程可见。volatile关键字的作用主要有两个,一个是保证变量的变化被所有线程可见(具体原因和java内存模型有关:jvm中的主存被所有线程共用,每个线程有自己的工作内存,共用内存的某些变量会在各个线程有一份拷贝,volatile是一个轻量级的同步操作,会将主存中变量的变化通知到各线程中,更改各线程中的拷贝);另一个是防止编译重排序。
如果我们和其他几个Atomic类比较会发现,他们都有一个unsafe 和offset的成员变量。
这里unsafe并不是线程不安全的意思,而是java要通过这个类执行非java语言实现的一些系统功能(比如compare and swap,大部分内核厂商提供了这样的方法接口),这些代码是危险的native方法,所以是unsafe。如注释所说,这个变量主要用于更新value。
offset是当前对象中value的偏移量,大小是这个value要代表的对象的大小,在这个类初始化的时候通过static方法块为其赋初值。
/** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } /** * Creates a new AtomicInteger with initial value {@code 0}. */ public AtomicInteger() { } /** * Gets the current value. * * @return the current value */ public final int get() { return value; } /** * Sets to the given value. * * @param newValue the new value */ public final void set(int newValue) { value = newValue; } /** * Eventually sets to the given value. * * @param newValue the new value * @since 1.6 */ public final void lazySet(int newValue) { unsafe.putOrderedInt(this, valueOffset, newValue); } /** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); }
AtomicInteger提供了构造方法为value赋初值,而且提供了针对这个value的get,set方法。另外两个很有意思的方法是getAndSet和lasySet。他们分别通过调用unsafe的getAndSetInt和putOrderedInt实现。这两个方法使用场景不多,getAndSet是可以原子的写入这个新值同时返回旧值。而lazySet是直接在对象的偏移地址写入新值,但这个新值因为延迟不会立即被其他线程可见,但终究会被其他线程可见。lazySet的性能要比直接set好,但除非真的有非常高的性能要求,否则不推荐使用,毕竟多线程编程安全性更重要。
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * <p><a href="package-summary.html#weakCompareAndSet">May fail * spuriously and does not provide ordering guarantees</a>, so is * only rarely an appropriate alternative to {@code compareAndSet}. * * @param expect the expected value * @param update the new value * @return {@code true} if successful */ public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
然后就是我们最常用的compareAndSet,这个方法有两个输入expect和update,unsafe会比较当前value和expect的值,相等的话更新为update并返回true,不相等不更新并返回false。这里另外一个有意思的方法叫weakCompareAndSet,它的实现和compareAndSet一模一样但注释说这个方法可能失败。猜测应该是jdk以后会补全这个实现。
其余方法及说明如下表:
这里的所有方法线程安全,其中前四个使用unsafe的native方法,后四个都调用compareAndSet进行更新操作,不成功的话会一直重试,直至get到的值compare成功。
这里有IntUnaryOperator和IntBinaryOperator可能造成困扰。IntUnaryOperator就是针对自身的一个计算操作,IntBinaryOperator为自身对另一个数的计算操作,如:
IntUnaryOperator i = (x) -> x*x;System.out.println(i.applyAsInt(2));4IntBinaryOperator io = (x,y)->x +y;System.out.println(io.applyAsInt(2,3));5
其他Atomic类和AtomicInteger类似,其中AtomicLong和AtomicInteger是基本一样的,而AtomicBoolean会更简单。而AtomicReference中,value变成一个可变类型,但这里有问题,后边会说。
AtomicIntegerArray中通过base和shift进行位运算得到每个数组下标的内存offset,而AtomicInteger中的value在这里替换成int[] array,值得注意的是这个数组不是volatile的,如果一个数组被volatile修饰,那么只能保护这个数组的引用,一旦引用指向的数组改变,这个保护就有问题了。一句话来说,针对数组来说volatile只能保护其引用,不能保护其元素。所以AtomicIntegerArray的方法都有一个i参数,表示要操作的元素下标。直接get时在getRow中通过unsafe.getIntVolatile(array, offset);方法保证单个元素的可见性。
CAS潜在的一些坑
CAS在一些情况下不能保证线程安全,比如常见的ABA场景:先获取了旧值A,假设A是一个对象,其中有一些状态。然后CAS(A,B),操作成功,这时当前值变成B,如果此时有其他线程修改了A的状态,然后CAS(B,A)。当前值变成A,这个A和之前的A引用是一样的,但是实际状态不同了。此时如果有其他线程再来做CAS(A,C),依旧会成功,但这显然是不正确的。AtomicReference就存在这个问题,这也是它不叫AtomicObject的原因,因为这里保护的只有引用。在concurrent里,AtomicStampedReference解决了这一问题,方法很巧妙,它把需要维护的reference和其时间戳做了一个pair放在一起,这样在compare的时候不仅要比较reference,还要比较时间戳,从而解决了ABA问题。
- java concurrent
- Java.concurrent.*
- java concurrent
- java concurrent
- Java Concurrent
- java concurrent
- Java Concurrent
- java concurrent
- java concurrent
- java concurrent
- java concurrent
- 【java】 java.util.concurrent
- concurrent包:java.util.concurrent.atomic
- java.util.concurrent.CyclicBarrier
- java concurrent code sample
- 主题:实战java Concurrent
- java.util.concurrent 学习
- java.util.concurrent包
- java集合List的功能
- nslookup在静态编译的busybox上如何正常解析域名
- Intellij idea 功能简介(三)主页设置及功能简介
- java 批处理sql语句
- JSTL标签库学习笔记
- java concurrent
- 【云星数据---Apache Flink实战系列(精品版)】:Apache Flink批处理API详解与编程实战026--DateSet实用API详解026
- 地图投影和矢量瓦片详解
- SpringMvc之值获取Session的两种方法-yellowcong
- 怎样获取form-data方式POST的数据
- bitnami redmine3.2.0插件开发
- js获取当前时间是本年第几周
- 设计模式入门篇——EIT造型
- 【云星数据---Apache Flink实战系列(精品版)】:Apache Flink批处理API详解与编程实战027--DateSet实用API详解027