java并发编程:Volatile关键字和Atomic类

  在学习并发编程的时候一定要了解Volatile关键字和Atomic类。现在让我们来逐一了解。

1 Volatile关键字

  要想详细了解Volatile关键字就必须了解一下线程之间的可见性,什么是线程可见性呢?

1.1 可见性

  可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程的修改结果,另一个线程马上能看到。下面举一个代码列子来说明:

public class UseVolatitle extends Thread {
    private boolean isrunning = true;

    public void setIsrunning(boolean isrunning) {
        this.isrunning = isrunning;
    }
    @Override
    public void run() {
        System.out.println("开始启动线程");
        while(isrunning) {
        }
        System.out.println("线程结束");
    }
    public static void main(String[] args) throws Exception {
        // 新建类实例
        UseVolatitle useVolatitle = new UseVolatitle();
        // 启动线程方法
        useVolatitle.start();
        // 线程睡眠2000毫秒,也就是2分钟。
        Thread.sleep(2000);
        // 把线程中isrunning值设置为false
        useVolatitle.setIsrunning(false);
        System.out.println("主线线程将running设置为false");
    }
}

  该程序的运行结果如下:

开始启动线程
主线线程将running设置为false

  虽然打印出了两条语句但是控制台没有停止,死循环依然卡在哪里。也就是主线程对相关线程isrunning的值改写,没有被相关线程可见,当试图把变量加上volatile关键字。代码如下:

public class UseVolatitle extends Thread {
    private volatile boolean isrunning = true;

    public void setIsrunning(boolean isrunning) {
        this.isrunning = isrunning;
    }

    @Override
    public void run() {
        System.out.println("开始启动线程");
        while(isrunning) {
        }
        System.out.println("线程结束");
    }

    public static void main(String[] args) throws Exception {
        UseVolatitle useVolatitle = new UseVolatitle();
        useVolatitle.start();
        Thread.sleep(2000);

        useVolatitle.setIsrunning(false);
        System.out.println("主线线程将running设置为false");
    }
}

  运行结果如下:

开始启动线程
主线线程将running设置为false
线程结束

  再次运行,虽然卡着几秒钟,但最终程序能够正常终止。这就是volatile作用。

2 Atomic类

  Atomic类就是为了实现原子性,主要的核心类有:1、AtomicInteger;2、AtomicLong;3、AtomicBoolean;4、AtomicIntegerArray;5、AtomicLongArray;6、AtomicReference。首先了解一下原子性。

2.1 原子性

  原子性操作的最小单位,例如转账业务:从农业银行转1块钱到工商银行。首先从用户的农业银行A帐号扣除1块钱,然后再向用户所在的工商银行帐号B新增1块钱。这两个操作要么同时成功,要么同时失败。下面举一个代码例子来深入了解Atomic类来操作原子性。

public class NoUseAtomic {
    static int count = 0;
    public int add() {
        count = count+10;
        return count;
    }
    public static void main(String[] args) {
        NoUseAtomic noUseAtomic = new NoUseAtomic();
        List list = new ArrayList();
        for(int i = 0; i < 100; i++) {
            list.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(noUseAtomic.add());
                }
            }));
        }
        for(Thread t : list) {
            t.start();
        }
    }
}

  控制台输出结果如下:

10
20
30
40
......
960

  如果你仔细观察,你会发现那个该死1000没有出现,虽然这100个线程执行先后顺序我们是不可预测的,但是我总是希望在某个线程的某个时刻输出期待已久的1000,但现实总是令人悲伤的。现在让我们来给我们的count变量替换Atomic类,使之能出现我们预期的结果。相关代码如下:

public class UseAtomic {

    static AtomicInteger count = new AtomicInteger(0);

    public int add() {
        count.addAndGet(10);
        return count.get();
    }

    public static void main(String[] args) {
        UseAtomic noUseAtomic = new UseAtomic();
        List list = new ArrayList();
        for(int i = 0; i < 100; i++) {
            list.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(noUseAtomic.add());
                }
            }));
        }
        for(Thread t : list) {
            t.start();
        }
    }
}

  运行上诉代码,虽然输出的先后顺序也同样是不可预期的,但是在众多的输出中,你会发现1000被打印出来。

注:(1)并非所有的Java基本数据类型都具有其原子类,比如并不存在AtomicChar,AtomicFloat和AtomicDouble等。
(2)虽然Atomic是原子性的,但是add方法并发原子性的。

2.2 方法原子性(synchronized)

  1.1讲到add方法并发原子性,举代码例子说明如下:

public class WarmingInAtomic {
    static AtomicInteger count = new AtomicInteger(0);
    public int add() {
        count.addAndGet(1);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        return count.get();
    }

    public static void main(String[] args) {
        WarmingInAtomic warmingInAtomic = new WarmingInAtomic();
        List list = new ArrayList();
        for(int i = 0; i < 100; i++) {
            list.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(warmingInAtomic.add());
                }
            }));
        }
        for(Thread t : list) {
            t.start();
        }
    }
}

  运行的部分结果如下:

109
118
127
136
145
154
163
172

  运行结果之间的最小间隔是1,这就是并发原子性造成的。如果方法上加上关键字synchronized,则保持原子性,相关代码如下:

public class WarmingInAtomic {

    static AtomicInteger count = new AtomicInteger(0);

    public synchronized int add() {
        count.addAndGet(1);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        count.addAndGet(1);
        return count.get();
    }

    public static void main(String[] args) {
        WarmingInAtomic warmingInAtomic = new WarmingInAtomic();
        List list = new ArrayList();
        for(int i = 0; i < 100; i++) {
            list.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(warmingInAtomic.add());
                }
            }));
        }
        for(Thread t : list) {
            t.start();
        }
    }
}

  输出部分结果如下:

10
20
30
40
50
60
70
80
90
100

2.3 类原子性(AtomicReference)

  当我们在主线程利用其它线程修改类属性,输出一直读的是主线程的类属性。相关代码如下:

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class UseAtomicRefrence1 {
    static Person person;
    public static void main(String[] args) throws InterruptedException {
        UseAtomicRefrence1 uar = new UseAtomicRefrence1();
        person = new Person();
        person.setName("root");
        person.setAge(18);
        Thread t1 = new Thread(new Task1());
        Thread t2 = new Thread(new Task2());
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        Thread.sleep(1000);
        System.out.println("now value : " + person.toString());
    }

    static class Task1 implements Runnable {
        @Override
        public void run() {
            person.setAge(19);
            person.setName("admin");

            System.out.println("thread1 value : " + person.toString());
        }
    }

    static class Task2 implements Runnable {
        @Override
        public void run() {
            person.setAge(20);
            person.setName("super user");

            System.out.println("thread2 value : " + person.toString());
        }
    }
}

  这样做是不能保证数据的一致性的,控制台输出如下:

thread1 value : Person{name='admin', age=19}
thread2 value : Person{name='super user', age=20}
now value : Person{name='super user', age=20}

  在类相应的对象初始化时用上AtomicReference属性,相关代码如下:

public class UseAtomicRefrence2 {
    //普通引用
    static Person person;

    //原子性引用
    static AtomicReference aPerson;

    public static void main(String[] args) throws InterruptedException {
        UseAtomicRefrence2 uar = new UseAtomicRefrence2();
        //new一个person类
        person = new Person();
        person.setName("root");
        person.setAge(18);
        //new一个AtomicReference原子性引用
        aPerson = new AtomicReference<>(person);
        System.out.println("启动其他线程之前主线程的值:" + aPerson.get());

        Thread t1 = new Thread(new Task1());
        Thread t2 = new Thread(new Task2());
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        Thread.sleep(1000);
        System.out.println("主线程当前值 : " + person.toString());
    }

    static class Task1 implements Runnable {
        @Override
        public void run() {
            Person expect = aPerson.get();
            Person update = new Person("admin", 19);
            //cas原子性操作
            aPerson.compareAndSet(expect, update);

            System.out.println("thread1 value : " + aPerson.get());
        }
    }

    static class Task2 implements Runnable {
        @Override
        public void run() {
            Person expect = aPerson.get();
            Person update = new Person("super user", 20);
            //cas原子性操作
            aPerson.compareAndSet(expect, update);

            System.out.println("thread2 value : " + aPerson.get());
        }
    }
}

  控制台输出如下:

启动其他线程之前主线程的值:Person{name='root', age=18}
thread1 value : Person{name='admin', age=19}
thread2 value : Person{name='super user', age=20}
主线程当前值 : Person{name='root', age=18}

  从上面的控制题看出,主线程,thread1,thread2线程分别对person对象的设置能在各自的线程中打印出来,并且thread1,thread2对静态变量的修改对主线程的对象属性值没有影响,保证了线程的安全。

你可能感兴趣的:(Java)