Java学习笔记—多线程(原子类,java.util.concurrent.atomic包,转载)

原子类

Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中

的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。

因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更

新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。

Atomic包里的类基本都是使用Unsafe实现的包装类

java.util.concurrent.atomic中的类可以分成4组:

  • 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference

CAS

CAS(Compare-And-Swap)算法保证数据操作的原子性。

CAS 算法是硬件对于并发操作共享数据的支持。

CAS 包含了三个操作数:

  内存值 V

  预估值 A

  更新值 B

当且仅当 V == A 时,V 将被赋值为 B,否则循环着不断进行判断 V 与 A 是否相等。

CAS的全称为Compare-And-Swap,是一条CPU的原子指令,其作用是让CPU比较后原子地更新某个位置的值,经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。

atomic包的原子包下使用这种方案。例如AtomicInteger的getAndIncrement自增+1的方法,经查看源码如下:

public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

源码中for循环体的第一步先取得AtomicInteger里存储的数值,第二步对AtomicInteger的当前数值进行加1操作,关键的第三步调用compareAndSet方法来进行原子更新操作,该方法先检查当前数值是否等于current,等于意味着AtomicInteger的值没有被其他线程修改过,则将AtomicInteger的当前数值更新成next的值,如果不等compareAndSet方法会返回false,程序会进入for循环重新进行compareAndSet操作

Unsafe只提供了3种CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong,具体实现都是使用了native方法。

/**
* 如果当前数值是expected,则原子的将Java变量更新成x
* @return 如果更新成功则返回true
*/
public final native boolean compareAndSwapObject(Object o,
long offset,
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);

native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

JNI是Java本机接口(Java Native Interface),是一个本机编程接口,它是Java软件开发工具箱(java Software Development Kit,SDK)的一部分。JNI允许Java代码使用以其他语言编写的代码和代码库。Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。

标量类

  • AtomicBoolean:原子更新布尔变量
  • AtomicInteger:原子更新整型变量
  • AtomicLong:原子更新长整型变量
  • AtomicReference:原子更新引用类型

具体到每个类的源代码中,提供的方法基本相同,这里以AtomicInteger为例进行说明,AtomicInteger源码主要的方法如下,原理主要都要都是采用了CAS,这里不再累述。

public class AtomicInteger extends Number implements java.io.Serializable {

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
 //返回当前的值
    public final int get() {
        return value;
    }
    //最终会设置成新值
    public final void set(int newValue) {
        value = newValue;
    }

    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
      //原子更新为新值并返回旧值
    public final int getAndSet(int newValue) {
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
     //如果输入的值等于预期值,则以原子方式更新为新值
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

 //原子自增
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

//原子方式将当前值与输入值相加并返回结果
    public final int getAndAdd(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return current;
        }
    }
}

数组类

  • AtomicIntegerArray:原子更新整型数组的某个元素
  • AtomicLongArray:原子更新长整型数组的某个元素
  • AtomicReferenceArray:原子更新引用类型数组的某个元素

AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下。

  • int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

以上几个类提供的方法几乎一样,所以这里以AtomicIntegerArray为例进行讲解

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;

    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }

    public AtomicIntegerArray(int length) {
        array = new int[length];
    }

    public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }

    private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
    }

    public final void set(int i, int newValue) {
        unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
    }

    public final void lazySet(int i, int newValue) {
        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
    }

    public final int getAndSet(int i, int newValue) {
        long offset = checkedByteOffset(i);
        while (true) {
            int current = getRaw(offset);
            if (compareAndSetRaw(offset, current, newValue))
                return current;
        }
    }

    public final boolean compareAndSet(int i, int expect, int update) {
        return compareAndSetRaw(checkedByteOffset(i), expect, update);
    }

    private boolean compareAndSetRaw(long offset, int expect, int update) {
        return unsafe.compareAndSwapInt(array, offset, expect, update);
    }

}

这里关键的实现也还是使用了CAS的策略,具体通过unsafe的native方法进行实现

原子更新字段类

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供

了以下3个类进行原子字段更新。

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起

    来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的

    ABA问题。

    要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类,每次使用的

    时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第

    二步,更新类的字段(属性)必须使用public volatile修饰符

public class AtomicIntegerFieldUpdaterTest {
// 创建原子更新器,并设置需要更新的对象类和对象的属性
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "old");
public static void main(String[] args) {
// 设置柯南的年龄是10岁
User conan = new User("conan", 10);
// 柯南长了一岁,但是仍然会输出旧的年龄
System.out.println(a.getAndIncrement(conan));
// 输出柯南现在的年龄
System.out.println(a.get(conan));
}
public static class User {
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}

代码执行后输出如下。

10
11

转载自:

Java并发编程-原子类及并发工具类

原文地址:https://www.cnblogs.com/Jason-Xiang/p/8449512.html

时间: 2024-11-06 04:54:34

Java学习笔记—多线程(原子类,java.util.concurrent.atomic包,转载)的相关文章

Java学习笔记—多线程(java.util.concurrent并发包概括,转载)

一.描述线程的类:Runable和Thread都属于java.lang包 二.内置锁synchronized属于jvm关键字,内置条件队列操作接口Object.wait()/notify()/notifyAll()属于java.lang包 二.提供内存可见性和防止指令重排的volatile属于jvm关键字 四.而java.util.concurrent包(J.U.C)中包含的是java并发编程中有用的一些工具类,包括几个部分: 1.locks部分:包含在java.util.concurrent.

JAVA学习笔记 -- 多线程之共享资源

在多线程程序运行过程中,可能会涉及到两个或者多个线程试图同时访问同一个资源.为了防止这种情况的发生,必须在线程使用共享资源时给资源"上锁",以阻挡其它线程的访问.而这种机制也常常被称为互斥量,本文主要介绍它的两种方式synchronized和Lock . 1.synchronized 当任务要执行被synchronized关键字保护的代码片段的时候,它会检查锁是否可用,然后获取锁,执行代码,释放锁.synchronized也有两种用法: A.synchronized方法 import

java学习笔记(三)java中的修饰符abstract、static与final

一.四种访问级别的访问范围 访问级别 访问修饰符 同类 同包 子类 不同的包 公开 public 受保护 protected 默认 没有访问修饰符 私有的 private 二.使用abstract修饰符需要遵守的语法规则 1.抽象类中可以没有抽象方法,但是包含抽象方法的类必须定义为抽象类,即用abstract修饰: 2.抽象类跟抽象方法不能被final修饰符修饰: 3.抽象类中可以有非抽象方法,因为继承抽象类的子类必须重写父类中所有的抽象方法,因此抽象类中不能有抽象构造方法和抽象静态方法: 4.

关于java.util.concurrent.atomic.*包下面的线程问题

众所周知,jdk的java.util.concurrent.atomic包下得原子类都是线程安全的,这是正确的,但是如果如果这样设置变量,会出现什么问题呢: AtomicReference<Integer[]> ar = new AtomicReference<Integer[]>(); 可能结果不是你所想的是同步了,为什么会出现这种情况呢? 其实有一个很好的例子可以解释这个问题,大家都知道i++,这个自增的步骤就是读取旧值-->给这个值加1-->更新这个值散步组成,因

《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的AomicStampedReferrence,它不是AomicReferrence的子类,而是利用AomicReferrence实现的一个储存引用和Integer组的扩展类 首先,所有原子操作都是依赖于sun.misc.Unsafe这个类,这个类底层是由C++实现的,利用指针来实现数据操作 关于CAS

Java学习笔记—多线程(并发工具类,java.util.concurrent.atomic包)

在JDK的并发包里提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程间交换数据的一种手段.本章会配合一些应用场景来介绍如何使用这些工具类. CountDownLatch CountDownLatch允许一个或多个线程等待其他线程完成操作.假如有这样一个需求:我们需要解析一个Excel里多个sheet的数据,此时可以考虑使用多线程,每个线程解析一个sheet里的数据,

Java学习笔记—多线程(java.util.concurrent.locks包,转载)

一.synchronized的缺陷 synchronized是java中的一个关键字,也就是说是Java语言内置的特性.那么为什么会出现Lock呢? 如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有: 2)线程执行发生异常,此时JVM会让线程自动释放锁. 那么如果这个获取锁的线程由于要等待IO或者其他原因(比

Java学习笔记---多线程同步的五种方法

一.引言 前几天面试,被大师虐残了,好多基础知识必须得重新拿起来啊.闲话不多说,进入正题. 二.为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常.举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块.假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚.因此多线程同步就

java.util.concurrent.atomic 包详解

Atomic包的作用: 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心: Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作 关于CAS compare and swap,比较和替换技术,将预期值与当前变量的值比较(compare),如果相等则使用新值替换(swap)当前变量,否则不作操作: 现代CPU已广泛支持CAS指令,如果不支持,那么JVM将使用自旋锁,与互斥锁一样,两者都需先获取锁才能访问共享资源,但互斥锁会导致线程进入睡眠,而自旋锁会一