一 概述

  1. 在Java的并发编程中,如果在多线程执行下,在同一时间操作共享变量,很容易出现安全问题。如执行i++,很可能会得到不正确的值
  2. 通常情况我们的解决办法是通过synchronized关键字来控制线程的执行顺序,虽然可能得到正确的值,但是由于synchronized使用的悲观锁的方式,性能比较低下
  3. 在atomic包下,Java提供了一些列的原子类,来保证多线程安全的更新这些基本类型的变量,数组元素,引用类型,以及更新对象中的字段类型
  4. 原子类采用的乐观锁的策略,在Java中则是使用了CAS操作实现
  5. atomic包下一共有17个Java类

    二 原子类介绍

    2.1 操作基本类型的原子类

    在 atomic 包下,涉及到基本数据的原子操作类有 3 个:AtomicInteger,AtomicLong,AtomicBoolean。这个三个类分别提供了对整型,长整型,布尔类型的原子操作,以AtomicInteger为例

    1. // 获取当前值,然后自加,相当于i++
    2. getAndIncrement()
    3. // 获取当前值,然后自减,相当于i--
    4. getAndDecrement()
    5. // 自加1后并返回,相当于++i
    6. incrementAndGet()
    7. // 自减1后并返回,相当于--i
    8. decrementAndGet()
    9. // 获取当前值,并加上预期值
    10. getAndAdd(int delta)
    11. // 获取当前值,并设置新值
    12. int getAndSet(int newValue)

    使用多线程来操作一个共享对象的int类型的属性,会出现线程安全问题

    1. public class IntegerTest {
    2. private int num=0;
    3. public static void main(String[] args) throws InterruptedException {
    4. new IntegerTest().test1();
    5. }
    6. // 多线程操作基本整形数据
    7. public void test1() throws InterruptedException {
    8. IntegerTest test = new IntegerTest();
    9. for (int i = 0; i < 10000; i++) {
    10. new Thread() {
    11. @Override
    12. public void run() {
    13. test.num++;
    14. }
    15. }.start();
    16. }
    17. for (int i = 0; i < 10000; i++) {
    18. new Thread() {
    19. @Override
    20. public void run() {
    21. test.num--;
    22. }
    23. }.start();
    24. }
    25. // 最后的结果不一定为0,可能是负数,也可能是正数
    26. TimeUnit.SECONDS.sleep(5);
    27. System.out.println(test.num);
    28. }
    29. }

    使用 AtomicInteger 类来代替int类型,在多线程并发下,就不会有并发问题了
    AtomicInteger 可以使用 get() set() 方法获取值和设置值 ```java package com.lz.javase.juc.atomic;

import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger;

public class IntegerTest {

  1. private int num = 0;
  2. private final AtomicInteger atomicNum = new AtomicInteger();
  3. public static void main(String[] args) throws InterruptedException {
  4. IntegerTest test = new IntegerTest();
  5. test.test1();
  6. test.test2();
  7. }
  8. // 多线程操作基本整形数据
  9. public void test1() throws InterruptedException {
  10. IntegerTest test = new IntegerTest();
  11. for (int i = 0; i < 10000; i++) {
  12. new Thread() {
  13. @Override
  14. public void run() {
  15. test.num++;
  16. }
  17. }.start();
  18. }
  19. for (int i = 0; i < 10000; i++) {
  20. new Thread() {
  21. @Override
  22. public void run() {
  23. test.num--;
  24. }
  25. }.start();
  26. }
  27. TimeUnit.SECONDS.sleep(5);
  28. System.out.println(test.num);
  29. }
  30. // 多线程操作原子类整形
  31. public void test2() throws InterruptedException {
  32. IntegerTest test = new IntegerTest();
  33. test.atomicNum.set(0);
  34. for (int i = 0; i < 10000; i++) {
  35. new Thread() {
  36. @Override
  37. public void run() {
  38. test.atomicNum.getAndIncrement();
  39. }
  40. }.start();
  41. }
  42. for (int i = 0; i < 10000; i++) {
  43. new Thread() {
  44. @Override
  45. public void run() {
  46. test.atomicNum.getAndDecrement();
  47. }
  48. }.start();
  49. }
  50. TimeUnit.SECONDS.sleep(5);
  51. System.out.println(test.atomicNum.get());
  52. }

}

  1. <a name="YrtXv"></a>
  2. ## 2.2 操作引用类型的原子类
  3. 因为上面那些操作基本类型的原子类,只能操作一个基本类型变量,如果想要操作多个变量的话,则需要使用到这三个原子类:**AtomicReference**、**AtomicStampedReference**、**AtomicMarkableReference**
  4. 1. AtomicReference:普通的操作引用类型变量的原子类
  5. 1. AtomicStampedeReference:在对引用类型变量进行操作时,带上版本号。这个类将整数值和引用关联起来,可以用于解决原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
  6. 1. AtomicMarkableReference:在进行原子操作时,带上标记。该类将bool类型标记和引用关联起来
  7. 以AtomicReference为例,然后进行比较交换
  8. ```java
  9. package com.lz.javase.juc.atomic;
  10. import java.util.concurrent.atomic.AtomicReference;
  11. public class AtomicReferenceTest {
  12. public static void main(String[] args) {
  13. AtomicReferenceTest test = new AtomicReferenceTest();
  14. test.test1();
  15. }
  16. public void test1() {
  17. AtomicReference<Person> atomicReference = new AtomicReference<>();
  18. Person p1 = new Person();
  19. p1.setName("p1");
  20. Person p2 = new Person();
  21. p2.setName("p2");
  22. atomicReference.set(p1);
  23. atomicReference.compareAndSet(p1, p2);
  24. System.out.println(atomicReference.get());
  25. }
  26. }

2.3 操作数组的原子类

这里说的操作数组,并不是对数组本身的操作,而是对数组元素的操作。主要涉及到三个类:AtomicIntegerArray、AtomicLongArray及AtomicReferenceArray。分别对应整形数组,长整型数组,引用数组

  1. public class AtomicIntegerArray implements java.io.Serializable {
  2. // final类型的int数组
  3. private final int[] array;
  4. // 获取数组中第i个元素
  5. public final int get(int i) {
  6. return (int)AA.getVolatile(array, i);
  7. }
  8. // 设置数组中第i个元素
  9. public final void set(int i, int newValue) {
  10. AA.setVolatile(array, i, newValue);
  11. }
  12. // CAS更改第i个元素
  13. public final boolean compareAndSet(int i, int expectedValue, int newValue) {
  14. return AA.compareAndSet(array, i, expectedValue, newValue);
  15. }
  16. // 获取第i个元素,并加1
  17. public final int getAndIncrement(int i) {
  18. return (int)AA.getAndAdd(array, i, 1);
  19. }
  20. // 获取第i个元素并减1
  21. public final int getAndDecrement(int i) {
  22. return (int)AA.getAndAdd(array, i, -1);
  23. }
  24. // 对数组第i个元素加1后再获取
  25. public final int incrementAndGet(int i) {
  26. return (int)AA.getAndAdd(array, i, 1) + 1;
  27. }
  28. // 对数组第i个元素减1后再获取
  29. public final int decrementAndGet(int i) {
  30. return (int)AA.getAndAdd(array, i, -1) - 1;
  31. }
  32. // ... 省略
  33. }

二 CAS算法

1)CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
2)CAS 是一种无锁的非阻塞算法的实现。
3)CAS 包含了 3 个操作数:

  1. 需要读写的内存值 V
  2. 进行比较的值 A
  3. 拟写入的新值 B

4)当且仅当 V == A 时,CAS 通过原子方式用新值 B = V 的值,否则不会执行任何操作。