美文网首页Java 杂谈java多线程
Java 多线程(九):Atomic 包

Java 多线程(九):Atomic 包

作者: 聪明的奇瑞 | 来源:发表于2018-06-09 16:47 被阅读43次

无锁意味着方法未加锁,直观表现为线程之间存在着交叉执行

非原子操作例如 number++; number = number + 1; 这些操作实际上分为好几步执行

Atomic 包介绍

  • Atomic 包是 JDK5 之后提供的,这个包提供了一系列原子类,这些类允许在多线程环境下,无锁的进行原子操作(当某个线程在执行 Atomic 方法时不会被其它线程打断,其它线程会一直等到该方法执行完成)
  • Atomic 底层是借助 CPU 的 CAS 指令来实现的

Atomic 的作用

为了更好的让大家明白 Atomic 的作用,下面通过两个案例来进行分析

AtomicInteger 实现多线程安全加法

  • Code 1 不能保证共享变量在多线程中的可见性、原子性
  • Code 2 只能保证共享变量在多线程中的可见性、不能保证原子性
  • Code 3 能保证共享变量在多线程中的可见性、原子性

Code 1(非线程安全)

  • 下面代码在多线程中不安全,理想的输出结果为 1000,但多运行几次输出结果可能为 990、980,这是因为共享变量 number 是非线程安全的,而产生的可见性、原子性问题导致的
public class Code1 {
    public static void main(String[] args) {

        Test test = new Test(0);

        for (int j = 0; j < 100; j++)
            new Thread(test).start();

        System.out.println(test.number);
    }

    public static class Test implements Runnable {

        public int number;

        public Test(int number) {
            this.number = number;
        }

        @Override
        public void run() {
            number = number + 1;
            number = number + 2;
            number = number + 3;
            number = number + 4;
        }
        
    }
}

Code 2 (使用 volatile,仍非线程安全)

  • 下面使用 volatile 修饰变量 number,volatile 可以保证共享变量 number 在多线程中的可见性,但不能保证原子性,因此 number 结果仍可能不正确(不为 1000)
public class Code2 {
    public static void main(String[] args) {

        Test test = new Test();

        for (int j = 0; j < 100; j++)
            new Thread(test).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("number------------"+test.number);
    }

    public static class Test implements Runnable {

        volatile public int number;

        public Test() {
            this.number = 0;
        }

        @Override
        public void run() {
            number = number + 1;
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
            number = number + 2;
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
            number = number + 3;
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
            number = number + 4;
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
        }
    }
}
  • 输出结果可能如下
.....
ThreadId-108 : 974
ThreadId-108 : 980
ThreadId-108 : 983
ThreadId-109 : 984
ThreadId-107 : 978
ThreadId-109 : 990
ThreadId-109 : 993
ThreadId-108 : 988
ThreadId-109 : 997
number------------997

Code 3 (使用 AtomicInteger,线程安全)

  • 使用 AtomicInteger 可以保证共享变量的可见性、操作原子性,因此 number 结果总为正确值(1000)
public class Code3 {
    public static void main(String[] args) {

        Test test = new Test(0);

        for (int j = 0; j < 100; j++)
            new Thread(test).start();
    }

    public static class Test implements Runnable {

        public AtomicInteger number;

        public Test(int number) {
            this.number = new AtomicInteger(number);
        }

        @Override
        public void run() {
            number.addAndGet(1);
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
            number.addAndGet(2);
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
            number.addAndGet(3);
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
            number.addAndGet(4);
            System.out.println("ThreadId-" + Thread.currentThread().getId() + " : " + number);
        }
    }
}
  • 输出结果可能如下
......
ThreadId-108 : 977
ThreadId-108 : 983
ThreadId-108 : 986
ThreadId-109 : 987
ThreadId-108 : 991
ThreadId-109 : 993
ThreadId-109 : 996
ThreadId-109 : 1000

AtomicBoolean 解决并发多次初始化问题

  • AtomicBoolean 的 compareAndSet(boolean expect, boolean update) 方法讲解:
    • 比较 AtomicBoolean 和 expect 的值,如果一致,执行方法内的语句(其实就是一个if语句),并把 AtomicBoolean 的值设成 update
    • 这两个动作之间不会被打断,任何内部或者外部的语句都不可能在两个动作之间运行,属于原子操作

Code 1(非并发安全)

  • 下面代码在多线程并发会存在多次初始化问题,原因可能是共享变量的可见性与线程争夺交叉执行导致的
public class Code1 {
    public static void main(String[] args) {
        Test test = new Test(true);
        for (int j = 0; j < 100; j++)
            new Thread(test).start();
    }

    public static class Test implements Runnable {

        public boolean flag;

        public Test(boolean flag) {
            this.flag = flag;
        }

        public void init() {
            if (flag) {
                System.out.println("初始化中1");
                System.out.println("初始化中2");
                System.out.println("初始化完毕");
                flag = false;
            }
        }

        @Override
        public void run() {
            init();
        }

    }
}
  • 输出结果可能如下,这是因为当线程 1 在执行 init() 初始化时线程 2 抢占到了 CPU 资源,并且也执行了 init() 方法,导致多次初始化
初始化中1
初始化中2
初始化完毕
初始化中1
初始化中2
初始化完毕

Code 2(使用 AtomicBoolean)

  • 使用 AtomicBoolean 的 compareAndSet 方法保证 compare 与 set 是原子级操作,可以保证 init() 只被调用一次
public class Code2 {
    public static void main(String[] args) {
        Test test = new Test(true);
        for (int j = 0; j < 100; j++)
            new Thread(test).start();
    }

    public static class Test implements Runnable {

        AtomicBoolean atomicBoolean;

        public Test(boolean flag) {
            this.atomicBoolean = new AtomicBoolean(flag);
        }

        public void init() {
            if (atomicBoolean.compareAndSet(true, false)) {
                System.out.println("初始化中1");
                System.out.println("初始化中2");
                System.out.println("初始化完毕");
            }
        }

        @Override
        public void run() {
            init();
        }

    }
}
  • 因此输出结果总是如下
初始化中1
初始化中2
初始化完毕

相关文章

网友评论

    本文标题:Java 多线程(九):Atomic 包

    本文链接:https://www.haomeiwen.com/subject/gqoteftx.html