深入理解java:2.3.1. 并发编程concurrent包 之Atomic原子操作

java中,可能有一些场景,操作非常简单,但是容易存在并发问题,比如i++,

此时,如果依赖锁机制,可能带来性能损耗等问题,

于是,如何更加简单的实现原子性操作,就成为java中需要面对的一个问题。

在backport-util-concurrent没有被引入java1.5并成为JUC之前,

这些原子类和原子操作方法,都是使用synchronized实现的。

不过JUC出现之后,这些原子操作 基于JNI提供了新的实现,

比如AtomicInteger,AtomicLong,AtomicBoolean,AtomicReference,AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray;

这些操作中提供一些原子化操作,比如incrementAndGet(相当于i++),compareAndSet(安全赋值)等,直接读源代码也很容易懂。

以AtomicInteger为例,看看它是怎么做到的:

如果是读取值,很简单,将value声明为volatile的,就可以保证在没有锁的情况下,数据是线程可见的:

1     private volatile int value;

     public final int get() {
2       return value;
3     }

那么,涉及到值变更的操作呢?以AtomicInteger实现:++i为例:

1     public final int incrementAndGet() {
2      for (;;) {
3        int current = get();
4        int next = current + 1;
5        if (compareAndSet(current, next))
6          return next;
7        }
8     } 

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

而这里的comparAndSet(current,next),就是前面介绍CAS的时候所说的依赖JNI实现的乐观锁做法:

    public final boolean compareAndSet(int expect, int update) {
       return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

数组原子化

注意,Java中Atomic*Array,并不是对整个数组对象实现原子化(也没有必要这样做),而是对数组中的某个元素实现原子化。

例如,对于一个整型原子数组,其中的原子方法,都是对每个元素的:

1 public final int getAndDecrement(int i) {
2       while (true) {
3        int current = get(i);
4        int next = current - 1;
5        if (compareAndSet(i, current, next))
6             return current;
7       }
8 } 

引用的原子化操作

引用的操作本身不就是原子的吗?

一个对象的引用,从A切换到B,本身也不会出现 非原子操作啊?这种想法本身没有什么问题,

但是考虑下嘛的场景:对象a,当前执行引用a1,

线程X期望将a的引用设置为a2,也就是a=a2,

线程Y期望将a的引用设置为a3,也就是a=a3。

X要求,a必须从a1变为a2,也就是说compareAndSet(expect=a1,setValue=a2);

Y要求,a必须从a1变为a3,也就是说compareAndSet(expect=a1,setValue=a3)。

如果严格遵循要求,应该出现X把a的引用设置为a2后,Y线程操作失败的情况,也就是说:

X:a==a1--> a=a2;

Y:a!=a1 --> Exception;

如果没有原子化,那么Y会直接将a赋值为a3,从而导致出现脏数据。

这就是原子引用AtomicReference存在的原因。

1      public final V getAndSet(V newValue) {
2            while (true) {
3                V x = get();
4                if (compareAndSet(x, newValue))
5                    return x;
6            }
7        }

注意,AtomicReference要求引用也是volatile的。

Updater原子化

其它几个Atomic类,都是对被volatile修饰的基本数据类型的自身数据进行原子化操作,

但是如果一个被volatile修饰的变量本身已经存在在类中,那要如何提供原子化操作呢?

比如,一个Person,其中有个属性为age,private volatile int age,

如何对age提供原子化操作呢?

1 private AtomicIntegerFieldUpdater<Person> updater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
2 updater.getAndIncrement(5);//加5岁
3 updater.compareAndSet(person, 30, 35)//如果一个人的年龄是30,设置为35。

Java中的Atomic包使用指南

参考: http://ifeve.com/java-atomic/

时间: 2024-12-15 12:06:31

深入理解java:2.3.1. 并发编程concurrent包 之Atomic原子操作的相关文章

深入理解java:2.3.4. 并发编程concurrent包 之容器ConcurrentLinkedQueue

1.    引言 在并发编程中我们有时候需要使用线程安全的队列. 如果我们要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法. 使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现, 而非阻塞的实现方式则可以使用循环CAS的方式来实现,本文让我们一起来研究下如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的. 2.    ConcurrentLinkedQueue的介绍 Concurre

深入理解java:2.3.3. 并发编程concurrent包 之容器ConcurrentHashMap

线程不安全的HashMap 因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap. 效率低下的HashTable容器 HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下. 因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态. 如线程1使用put进行添加元素,线程2不但不能使用pu

Java修炼之道--并发编程

原作地址:https://github.com/frank-lam/2019_campus_apply 前言 在本文将总结多线程并发编程中的常见面试题,主要核心线程生命周期.线程通信.并发包部分.主要分成 "并发编程" 和 "面试指南" 两 部分,在面试指南中将讨论并发相关面经. 参考资料: <Java并发编程实战> 第一部分:并发编程 1. 线程状态转换 新建(New) 创建后尚未启动. 可运行(Runnable) 可能正在运行,也可能正在等待 CPU

Java 面试宝典!并发编程 71 道题及答案全送上!

金九银十跳槽季已经开始,作为 Java 开发者你开始刷面试题了吗?别急,我整理了71道并发相关的面试题,看这一文就够了! 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on):true则把该线程设置为守护线程,反之则为用户线程.Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常. 两者

并发编程-concurrent指南-ConcurrentMap

ConcurrentMap 是个接口,你想要使用它的话就得使用它的实现类之一. ConcurrentMap,它是一个接口,是一个能够支持并发访问的java.util.map集合: 在原有java.util.map接口基础上又新提供了4种方法,进一步扩展了原有Map的功能: public interface ConcurrentMap<K, V> extends Map<K, V> { //插入元素 V putIfAbsent(K key, V value); //移除元素 bool

Java的并发神器concurrent包详解(一)

在JDK 1.5之前,提到并发,java程序员们一般想到的是wait().notify().Synchronized关键字等,但是并发除了要考虑竞态资源.死锁.资源公平性等问题,往往还需要考虑性能问题,在一些业务场景往往还会比较复杂,这些都给java coder们造成不小的难题.JDK 1.5的concurrent包帮我们解决了不少问题. Concurrent包中包含了几个比较常用的并发模块,这个系列,LZ就和大家一起来学习各个模块,Let's Go! 一.线程池的基本用法 一般并发包里有三个常

并发编程-concurrent指南-阻塞队列-延迟队列DelayQueue

DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走.这种队列是有序的,即队头对象的延迟到期时间最长.注意:不能将null元素放置到这种队列中. Delayed 一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象. 此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序. 下面例子是订单超时处理的具体代码: 重点是DelayOrderCompo

并发编程-concurrent指南-Lock-可重入锁(ReentrantLock)

可重入和不可重入的概念是这样的:当一个线程获得了当前实例的锁,并进入方法A,这个线程在没有释放这把锁的时候,能否再次进入方法A呢? 可重入锁:可以再次进入方法A,就是说在释放锁前此线程可以再次进入方法A(方法A递归). 不可重入锁(自旋锁):不可以再次进入方法A,也就是说获得锁进入方法A是此线程在释放锁钱唯一的一次进入方法A. ,具体区别查看可重入锁和不可重入锁区别. ReentrantLock,意思是"可重入锁".ReentrantLock是唯一实现了Lock接口的类,并且Reent

Java并发编程有多难?这几个核心技术你掌握了吗?

本文主要内容索引 1.Java线程 2.线程模型 3.Java线程池 4.Future(各种Future) 5.Fork/Join框架 6.volatile 7.CAS(原子操作) 8.AQS(并发同步框架) 9.synchronized(同步锁) 10.并发队列(阻塞队列) 本文仅分析java并发编程中的若干核心问题,对于上面没有提到但是又和java并发编程有密切关系的技术将会不断添加进来完善文章,本文将长期更新,不断迭代.本文试图从一个更高的视觉来总结Java语言中的并发编程内容,希望阅读完