Java Atomic 原子类


Atomic


1. AtomicInteger

a. 多线程并发访问问题

class MyThread extends Thread {
    public static volatile int a = 0;

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            //线程1:取出a的值a=0(被暂停)
            a++;
            //写回
        }
        System.out.println("修改完毕!");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //1.启动两个线程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("获取a最终值:" + MyThread.a);//最终结果仍然不正确。
    }
}
/*
输出
修改完毕!
修改完毕!
获取a最终值:14791
 */
  • 加入 volatile 关键字并不能解决原子性;

b. 用 AtomicInteger 类解决

  • 用 AtomicInteger 类解决了多线程的原子性问题,无论程序运行多少次,其结果总是正确的;
import java.util.concurrent.atomic.AtomicInteger;

class MyThread extends Thread {
    //public static volatile int a = 0;//不直接使用基本类型变量
    //改用"原子类"
    public static AtomicInteger a = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            // a++;
            a.getAndIncrement();//先获取,再自增1:a++
        }
        System.out.println("修改完毕!");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //1.启动两个线程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("获取a最终值:" + MyThread.a.get());
    }
}
/*
输出
修改完毕!
修改完毕!
获取a最终值:20000
 */

c. 工作原理:CAS 机制

  • 在 Unsafe 类中,调用了一个:compareAndSwapInt() 方法,此方法的几个参数:
    var1:传入的 AtomicInteger 对象
    var2:AtommicInteger 内部变量的偏移地址
    var5:之前取出的 AtomicInteger 中的值
    var5 + var4:预期结果
  • 此方法使用了一种"比较并交换(Compare And Swap)"的机制,它会用 var1 和 var2 先获取内存中 AtomicInteger 中的值,然后和传入的,之前获取的值 var5 做一下比较,也就是比较当前内存的值和预期的值是否一致,如果一致就修改为 var5 + var4,否则就继续循环,再次获取 AtomicInteger 中的值,再进行比较并交换,直至成功交换为止;
  • compareAndSwapInt() 方法是"线程安全"的;
  • 我们假设两个线程交替运行的情况,看看它是怎样工作的:
    初始 AtomicInteger 的值为0
    线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0
    线程A被暂停
    线程B执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0
    线程B执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
    线程B成功将 AtomicInteger 中的值改为1
    线程A恢复运行,执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
    此时线程A使用 var1 和 var2 从 AtomicInteger 中获取的值为:1,而传入的 var5 为0,比较失败,返回 false,继续循环
    线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:1
    线程A执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
    此时线程A使用 var1 和 var2 从 AtomicInteger 中获取的值为:1,而传入的var5为1,比较成功,将其修改为 var5 + var4,也就是2,将 AtomicInteger 中的值改为2,结束;
  • CAS 机制也被称为乐观锁,因为大部分比较的结果为 true,就直接修改了。只有少部分多线程并发的情况会导致 CAS 失败,而再次循环;

CAS


2. AtomicIntegerArray

a. 多线程并发访问问题

class MyThread extends Thread {
    private static int[] intArray = new int[1000];//不直接使用数组

    @Override
    public void run() {
        for (int i = 0; i < getIntArrayLength(); i++) {
            intArray[i]++;
        }
    }

    public static int getIntArray(int i) {
        return intArray[i];
    }

    public static int getIntArrayLength() {
        return intArray.length;
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();//创建1000个线程,每个线程为数组的每个元素+1
        }
        Thread.sleep(1000 * 5);//让所有线程执行完毕
        System.out.println("主线程休息5秒醒来");
        for (int i = 0; i < MyThread.getIntArrayLength(); i++) {
            System.out.println(MyThread.getIntArray(i));
        }
    }
}
/*
部分输出
...
999
999
1000
1000
...
 */
  • 可以发现,有些元素并不是1000;

b. 用 AtomicIntegerArray 类解决

  • AtomicIntegerArray 类可以保证数组的多线程安全;
import java.util.concurrent.atomic.AtomicIntegerArray;

class MyThread extends Thread {
    private static int[] intArray = new int[1000];//定义一个数组
    //改用原子类,使用数组构造
    public static AtomicIntegerArray arr = new AtomicIntegerArray(intArray);

    @Override
    public void run() {
        for (int i = 0; i < arr.length(); i++) {
            arr.addAndGet(i, 1);//将i位置上的元素+1
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();
        }
        Thread.sleep(1000 * 5);//让所有线程执行完毕
        System.out.println("主线程休息5秒醒来");
        for (int i = 0; i < MyThread.arr.length(); i++) {
            System.out.println(MyThread.arr.get(i));
        }
    }
}
/*
输出
1000
1000
1000
1000
...
 */
  • 可见,每次运行的结果都是正确的;
发布了185 篇原创文章 · 获赞 181 · 访问量 5349

猜你喜欢

转载自blog.csdn.net/Regino/article/details/104719868
今日推荐