上周被问到这个问题,没想出来,后来提示说concurrent包里的原子类。回来学习一下。

一、何谓Atomic?

Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的Atomic是指不能分割成若干部分的意思。如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的。通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成)
 
 在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
 
二、java.util.concurrent中的原子变量

无论是直接的还是间接的,几乎 java.util.concurrent 包中的所有类都使用原子变量,而不使用同步。类似 ConcurrentLinkedQueue 的类也使用原子变量直接实现无等待算法,而类似 ConcurrentHashMap 的类使用 ReentrantLock 在需要时进行锁定。然后, ReentrantLock 使用原子变量来维护等待锁定的线程队列。
如果没有 JDK 5.0 中的 JVM 改进,将无法构造这些类,这些改进暴露了(向类库,而不是用户类)接口来访问硬件级的同步原语。然后,java.util.concurrent 中的原子变量类和其他类向用户类公开这些功能
java.util.concurrent.atomic的原子类

这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。其中的类可以分成4组
AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
AtomicIntegerArray,AtomicLongArray
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
其中AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference是类似的。

首先AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference内部api是类似的:举个AtomicReference的例子

使用AtomicReference创建线程安全的堆栈
 
Java代码

public class LinkedStack<T> { 

    private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>(); 

    public T push(T e) {
        Node<T> oldNode, newNode;
        while (true) { //这里的处理非常的特别,也是必须如此的。
            oldNode = stacks.get();
            newNode = new Node<T>(e, oldNode);
            if (stacks.compareAndSet(oldNode, newNode)) {
                return e;
            }
        }
    } 

    public T pop() {
        Node<T> oldNode, newNode;
        while (true) {
            oldNode = stacks.get();
            newNode = oldNode.next;
            if (stacks.compareAndSet(oldNode, newNode)) {
                return oldNode.object;
            }
        }
    } 

    private static final class Node<T> {
        private T object; 

        private Node<T> next; 

        private Node(T object, Node<T> next) {
            this.object = object;
            this.next = next;
        }
    }
} 

然后关注字段的原子更新。

AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V>是基于反射的原子更新字段的值。

相应的API也是非常简单的,但是也是有一些约束的。

(1)字段必须是volatile类型的!volatile到底是个什么东西。请查看 
(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。

(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。

(5)对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。

在下面的例子中描述了操作的方法。

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;  

public class AtomicIntegerFieldUpdaterDemo {  

   class DemoData{
       public volatile int value1 = 1;
       volatile int value2 = 2;
       protected volatile int value3 = 3;
       private volatile int value4 = 4;
   }
    AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName) {
        return AtomicIntegerFieldUpdater.newUpdater(DemoData.class, fieldName);
    }
    void doit() {
        DemoData data = new DemoData();
        System.out.println("1 ==> "+getUpdater("value1").getAndSet(data, 10));
        System.out.println("3 ==> "+getUpdater("value2").incrementAndGet(data));
        System.out.println("2 ==> "+getUpdater("value3").decrementAndGet(data));
        System.out.println("true ==> "+getUpdater("value4").compareAndSet(data, 4, 5));
    }
    public static void main(String[] args) {
        AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo();
        demo.doit();
    }
}  

在上面的例子中DemoData的字段value3/value4对于AtomicIntegerFieldUpdaterDemo类是不可见的,因此通过反射是不能直接修改其值的。

AtomicMarkableReference类描述的一个<Object,Boolean>的对,可以原子的修改Object或者Boolean的值,这种数据结构在一些缓存或者状态描述中比较有用。这种结构在单个或者同时修改Object/Boolean的时候能够有效的提高吞吐量。

AtomicStampedReference类维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。对比AtomicMarkableReference类的<Object,Boolean>,AtomicStampedReference维护的是一种类似<Object,int>的数据结构,其实就是对对象(引用)的一个并发计数。但是与AtomicInteger不同的是,此数据结构可以携带一个对象引用(Object),并且能够对此对象和计数同时进行原子操作。

在本文结尾会提到“ABA问题”,而AtomicMarkableReference/AtomicStampedReference在解决“ABA问题”上很有用。

三、Atomic类的作用

使得让对单一数据的操作,实现了原子化
使用Atomic类构建复杂的,无需阻塞的代码
访问对2个或2个以上的atomic变量(或者对单个atomic变量进行2次或2次以上的操作)通常认为是需要同步的,以达到让这些操作能被作为一个原子单元。
无锁定且无等待算法

基于 CAS (compare and swap)的并发算法称为 无锁定算法,因为线程不必再等待锁定(有时称为互斥或关键部分,这取决于线程平台的术语)。无论 CAS 操作成功还是失败,在任何一种情况中,它都在可预知的时间内完成。如果 CAS 失败,调用者可以重试 CAS 操作或采取其他适合的操作。
如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作,就可以说该算法是 无等待的。与此形成对比的是, 无锁定算法要求仅 某个线程总是执行操作。(无等待的另一种定义是保证每个线程在其有限的步骤中正确计算自己的操作,而不管其他线程的操作、计时、交叉或速度。这一限制可以是系统中线程数的函数;例如,如果有 10 个线程,每个线程都执行一次CasCounter.increment() 操作,最坏的情况下,每个线程将必须重试最多九次,才能完成增加。)
再过去的 15 年里,人们已经对无等待且无锁定算法(也称为 无阻塞算法)进行了大量研究,许多人通用数据结构已经发现了无阻塞算法。无阻塞算法被广泛用于操作系统和 JVM 级别,进行诸如线程和进程调度等任务。虽然它们的实现比较复杂,但相对于基于锁定的备选算法,它们有许多优点:可以避免优先级倒置和死锁等危险,竞争比较便宜,协调发生在更细的粒度级别,允许更高程度的并行机制等等。
常见的:
非阻塞的计数器Counter
非阻塞堆栈ConcurrentStack
非阻塞的链表ConcurrentLinkedQueue

不用synchronized块的话如何实现一个原子的i++?的更多相关文章

  1. 用生活例子来解释Java synchronized块

    今天满世界的微信小程序的新闻,大家都说对于Android原生程序有构成危险了,我也不想了,以后的事谁知道呢, 我还是好好执行一下今年的计划吧.  项目刚刚上线,最近没啥事,我一直感觉自己的Java基础 ...

  2. Java基础知识强化之多线程笔记06:Lock接口 (区别于Synchronized块)

    1. 简介 我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式 ...

  3. Java多线程初学者指南(12):使用Synchronized块同步变量

    我们可以通过synchronized块来同步特定的静态或非静态方法.要想实现这种需求必须为这些特性的方法定义一个类变量,然后将这些方法的代码用synchronized块括起来,并将这个类变量作为参数传 ...

  4. Java多线程初学者指南(11):使用Synchronized块同步方法

    synchronized关键字有两种用法.第一种就是在<使用Synchronized关键字同步类方法>一文中所介绍的直接用在方法的定义中.另外一种就是synchronized块.我们不仅可 ...

  5. 使用Synchronized块同步变量

    我们可以通过synchronized块来同步特定的静态或非静态方法.要想实现这种需求必须为这些特定的方法定义一个类变量,然后将这些方法的代码用synchronized块括起来,并将这个类变量作为参数传 ...

  6. 使用Synchronized块同步方法

    synchronized关键字有两种用法.第一种就是在<使用Synchronized关键字同步类方法>一文中所介绍的直接用在方法的定义中.另外一种就是synchronized块.我们不仅可 ...

  7. Synchronized块同步变量的误区

    我们可以通过synchronized块来同步特定的静态或非静态方法.要想实现这种需求必须为这些特性的方法定义一个类变量,然后将这些方法的代码用synchronized块括起来,并将这个类变量作为参数传 ...

  8. synchronized块中的wait()、nofity()、nofityAll()方法

    前言 在Java并发编程实战,会经常遇到多个线程访问同一个资源的情况,这个时候就需要维护数据的一致性,否则会出现各种数据错误,其中一种同步方式就是利用Synchronized关键字执行锁机制,锁机制是 ...

  9. 对象中属性 加锁 用:volatile 关键词修饰 而 不用 synchronized 加锁

    一个对象中有一个状态 属性,现在业务需求 存在多线程来修改 和 拿去 这个状态 的值,这种情况如果加锁怎么加? 一种是 在 set 和get 这个状态的 方法那加 synchronized . 还有一 ...

随机推荐

  1. 安装DRBD的一些问题

    安装DRBD,建议用源代码包先生成rpm包来安装,不要用直接download的rpm包,有可能会用不了,因为这跟系统内核版本有关系,在2.6.33版本以前内核没有集成drbd,   A.先安装一些其它 ...

  2. thread_Semaphore信号量

    Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制.  使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数. 一个计数信 ...

  3. 比较body.onload(function())、$(document).ready(function())与$(windows).load(function)

    原理对比: body.onload(function())是优先将document的DOM渲染,即将页面所有的元素(包括html标签以及所引用到的图片,flash媒体等媒体文件)加载完成,然后再执行页 ...

  4. 安装win8、ubuntu双系统的过程

    弄了一个晚上,终于完成了,之前是用虚拟机的,但是觉得不带劲,并且折腾来时菜鸟变大神的捷径,虽然现在还一直在爬坑.继续奋斗吧...王小二 首先是看 ubuntu 百度贴吧的安装帖子(http://tie ...

  5. 具有timeout 功能的函数调用

    做项目的时候有时经常会需要一个带有timeout功能的函数调用. 比如从后台读数据并期望在给定时间内返回.借此机会包装了一个简单的C# class, 直接上代码吧. public class Time ...

  6. 重新想象 Windows 8.1 Store Apps (87) - TTS: Speak Text, Speak SSML

    [源码下载] 重新想象 Windows 8.1 Store Apps (87) - TTS: Speak Text, Speak SSML 作者:webabcd 介绍重新想象 Windows 8.1 ...

  7. SqlServer根据时段统计数据

    create table ST_PPTN_R_Real ( TID int primary key identity(1,1), STCD varchar(100) not null, TM date ...

  8. PHP的静态变量和引用函数

    直接贴代码,结果的原因写在备注了 <?php /** * Created by PhpStorm. * User: Administrator * Date: 16-8-25 * Time: 上 ...

  9. javamail 发送邮件demo

    package com.suntray.test; import javax.mail.BodyPart; import javax.mail.Message; import javax.mail.M ...

  10. Android项目实战(十二):解决OOM的一种偷懒又有效的办法

    在程序的manifest文件的application节点加入android:largeHeap=“true” 即可. 对,只需要一句话! 那么这行代码的意思是什么呢? 简单的说就是使该APP获取最大可 ...