美文网首页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