第一部分 Java基础
第二部分 Java进阶

Java多线程和并发面试题(附答案)第6题

● java.util.concurrent.atomic包

● AtomicBoolean原子性布尔

AtomicBoolean是java.util.concurrent.atomic包下的原子变量,这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排它性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。

AtomicBoolean,在这个Boolean值的变化的时候不允许在之间插入,保持操作的原子性。下面将解释重点方法并举例:

boolean compareAndSet(expectedValue, updateValue);

● 这个方法主要有两个作用:

比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句。

把AtomicBoolean的值设成update,比较最要的是这两件事是一气呵成的,这连个动作之间不会被打断,任何内部或者外部的语句都不可能在两个动作之间运行。为多线程的控制提供了解决的方案。

● 下面我们从代码上解释:

首先我们看下在不使用 AtomicBoolean 情况下,代码的运行情况:

package com.sxbdqn;

import java.util.concurrent.TimeUnit;

public class BarWorker implements Runnable {
    //静态变量
    private static boolean exists = false;

    private String name;

    public BarWorker(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        if (!exists) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e1) {
                // do nothing
            }
            exists = true;
            System.out.println(name + " enter");
            try {
                System.out.println(name + " working");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                // do nothing
            }
            System.out.println(name + " leave");
            exists = false;
        } else {
            System.out.println(name + " give up");
        }

    }
    
    public static void main(String[] args) {
        BarWorker bar1 = new BarWorker("bar1");
        BarWorker bar2 = new BarWorker("bar2");
        new Thread(bar1).start();
        new Thread(bar2).start();
    }
}

运行结果:

bar1 enter

bar2 enter

bar1 working

bar2 working

bar1 leave

bar2 leave

从上面的运行结果我们可看到,两个线程运行时,都对静态变量exists同时做操作,并没有保证exists静态变量的原子性,也就是一个线程在对静态变量exists进行操作到时候,其他线程必须等待或不作为。等待一个线程操作完后,才能对其进行操作。

下面我们将静态变量使用AtomicBoolean来进行操作。

package com.sxbdqn;

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

public class BarWorker2 implements Runnable {
    //静态变量使用 AtomicBoolean 进行操作
    private static AtomicBoolean exists = new AtomicBoolean(false);

    private String name;

    public BarWorker2(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        if (exists.compareAndSet(false, true)) {

            System.out.println(name + " enter");

            try {
                System.out.println(name + " working");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                // do nothing
            }
            System.out.println(name + " leave");
            exists.set(false);
        } else {
            System.out.println(name + " give up");
        }
    }

    public static void main(String[] args) {
        BarWorker2 bar1 = new BarWorker2("bar1");
        BarWorker2 bar2 = new BarWorker2("bar2");
        new Thread(bar1).start();
        new Thread(bar2).start();
    }
}

运行结果:

bar1 enter

bar1 working

bar2 give up

bar1 leave

可以从上面的运行结果看出仅仅一个线程进行工作,因为exists.compareAndSet(false,true)提供了原子性操作,比较和赋值操作组成了一个原子操作,中间不会提供可乘之机。使得一个线程操作,其他线程等待或不作为。

下面我们简单介绍下AtomicBoolean的API

● 你可以这样创建一个AtomicBoolean:

AtomicBoolean atomicBoolean = new AtomicBoolean();  

以上示例新建了一个默认值为false的AtomicBoolean。如果你想要为AtomicBoolean实例设置一个显式的初始值,那么你可以将初始值传给AtomicBoolean的构造子:

AtomicBoolean atomicBoolean = new AtomicBoolean(true); 

● 获得AtomicBoolean的值:

你可以通过使用get()方法来获取一个AtomicBoolean的值。示例如下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean value = atomicBoolean.get();

● 设置AtomicBoolean的值:

你可以通过使用set()方法来设置一个AtomicBoolean的值。示例如下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);
atomicBoolean.set(false);

以上代码执行后AtomicBoolean的值为false。

● 交换AtomicBoolean的值:

你可以通过 getAndSet()方法来交换一个AtomicBoolean实例的值。getAndSet()方法将返回AtomicBoolean当前的值,并将为AtomicBoolean设置一个新值。示例如下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean oldValue = atomicBoolean.getAndSet(false);

以上代码执行后oldValue变量的值为true,atomicBoolean实例将持有false值。代码成功将AtomicBoolean当前值ture交换为false。

● 比较并设置 AtomicBoolean 的值:

compareAndSet()方法允许你对AtomicBoolean的当前值与一个期望值进行比较,如果当前值等于期望值的话,将会对AtomicBoolean设定一个新值。compareAndSet()方法是原子性的,因此在同一时间之内有单个线程执行它。因此compareAndSet()方法可被用于一些类似于锁的同步的简单实现。以下是一个compareAndSet()示例:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean expectedValue = true; boolean newValue = false;
boolean wasNewValueSet = atomicBoolean.compareAndSet(expectedValue, newValue);

本示例对AtomicBoolean的当前值与true值进行比较,如果相等,将AtomicBoolean的值更新为false。

● AtomicInteger原子性整型

AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。

我们先来看看AtomicInteger给我们提供了什么方法:

ublic final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值

下面通过两个简单的例子来看一下AtomicInteger的优势在哪?

● 普通线程同步:

class Test2 {
    private volatile int count = 0;
    public synchronized void increment() {
        count++; //若要线程安全执行执行 count++,需要加锁
    }

    public int getCount() {
        return count;
    }
}

● 使用AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

class Test2 {
    private AtomicInteger count = new AtomicInteger();
    public void increment() {
        count.incrementAndGet();
    }
    //使用 AtomicInteger 之后,不需要加锁,也可以实现线程安全。
    public int getCount() {
        return count.get();
    }
}

从上面的例子中我们可以看出:使用AtomicInteger是非常安全的,而且因为AtomicInteger由硬件提供原子操作指令实现的,在非激烈竞争的情况下,开销更小,速度更快。AtomicInteger是使用非阻塞算法来实现并发控制的。AtomicInteger的关键域只有以下3个:

// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) {
        throw new Error(ex);
    }
}

private volatile int value;

这里,unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。valueOffset是用来记录value本身在内存的偏移地址的,这个记录也主要是为了在更新操作在内存中找到value的位置,方便比较。

注意:value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。

优点:最大的好处就是可以避免多线程的优先级倒置和死锁情况的发生,提升在高并发处理下的性能。

● 下面我们简单介绍下 AtomicInteger 的 API

● 创建一个 AtomicInteger 示例如下:

AtomicInteger atomicInteger = new AtomicInteger();

本示例将创建一个初始值为0的AtomicInteger。如果你想要创建一个给

定初始值的AtomicInteger,你可以这样:

AtomicInteger atomicInteger = new AtomicInteger(123);

本示例将123作为参数传给AtomicInteger的构造子,它将设置AtomicInteger实例的初始值为123。

● 获得AtomicInteger的值

你可以使用get()方法获取AtomicInteger实例的值。示例如下:

AtomicInteger atomicInteger = new AtomicInteger(123);
int theValue = atomicInteger.get();

● 设置AtomicInteger的值

你可以通过set()方法对AtomicInteger的值进行重新设置。以下是AtomicInteger.set()示例:

AtomicInteger atomicInteger = new AtomicInteger(123);
atomicInteger.set(234);

以上示例创建了一个初始值为123的AtomicInteger,而在第二行将其值更新为234。

● 比较并设置AtomicInteger的值

AtomicInteger类也通过了一个原子性的compareAndSet()方法。这一方法将AtomicInteger实例的当前值与期望值进行比较,如果二者相等,为AtomicInteger实例设置一个新值。 AtomicInteger.compareAndSet()代码示例:

AtomicInteger atomicInteger = new AtomicInteger(123);
int expectedValue = 123;
int newValue = 234;
atomicInteger.compareAndSet(expectedValue,newValue);

本示例首先新建一个初始值为123的AtomicInteger实例。然后将AtomicInteger与期望值123进行比较,如果相等,将AtomicInteger的值更新为234。

● 增加AtomicInteger的值

AtomicInteger类包含有一些方法,通过它们你可以增加AtomicInteger的值,并获取其值。这些方法如下:

public final int addAndGet(int addValue)//在原来的数值上增加新的值,并返回新值
public final int getAndIncrement()//获取当前的值,并自增
public final int incrementAndget() //自减,并获得自减后的值
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值

第一个addAndGet()方法给AtomicInteger增加了一个值,然后返回增加后的值。getAndAdd()方法为AtomicInteger增加了一个值,但返回的是增加以前的AtomicInteger的值。具体使用哪一个取决于你的应用场景。以下是这两种方法的示例:

AtomicInteger atomicInteger = new AtomicInteger();
System.out.println(atomicInteger.getAndAdd(10));
System.out.println(atomicInteger.addAndGet(10));

本示例将打印出0和20。例子中,第二行拿到的是加10之前的AtomicInteger的值。加10之前的值是0。第三行将AtomicInteger的值再加10,并返回加操作之后的值。该值现在是为20。你当然也可以使用这俩方法为AtomicInteger添加负值。结果实际是一个减法操作。getAndIncrement()和incrementAndGet()方法类似于getAndAdd()和addAndGet(),但每次只将AtomicInteger的值加1。

● 减小AtomicInteger的值

AtomicInteger类还提供了一些减小AtomicInteger的值的原子性方法。这些方法是:

public final int decrementAndGet()
public final int getAndDecrement()

decrementAndGet()将AtomicInteger的值减一,并返回减一后的值。getAndDecrement()也将AtomicInteger的值减一,但它返回的是减一之前的值。

● AtomicIntegerArray原子性整型数组

java.util.concurrent.atomic.AtomicIntegerArray类提供了可以以原子方式读取和写入的底层int数组的操作,还包含高级原子操作。AtomicIntegerArray支持对底层int数组变量的原子操作。它具有获取和设置方法,如在变量上的读取和写入。也就是说,一个集合与同一变量上的任何后续get相关联。原子compareAndSet方法也具有这些内存一致性功能。

AtomicIntegerArray本质上是对int[]类型的封装。使用Unsafe类通过CAS的方式控制int[]在多线程下的安全性。它提供了以下几个核心API:

//获得数组第 i 个下标的元素
public final int get(int i)
//获得数组的长度
public final int length()
//将数组第 i 个下标设置为 newValue,并返回旧的值
public final int getAndSet(int i, int newValue)
//进行 CAS 操作,如果第 i 个下标的元素等于 expect,则设置为 update,设置成功返回 true 
public final boolean compareAndSet(int i, int expect, int update)
//将第 i 个下标的元素加 1
public final int getAndIncrement(int i)
//将第 i 个下标的元素减 1
public final int getAndDecrement(int i)
//将第 i 个下标的元素增加 delta(delta 可以是负数)
public final int getAndAdd(int i,int delta)

下面给出一个简单的示例,展示 AtomicIntegerArray 使用:

public class AtomicIntegerArrayDemo {
    static AtomicIntegerArray arr = new AtomicIntegerArray(10);
    public static class AddThread implements Runnable {
        public void run() {
            for (int k = 0; k < 10000; k++) 
	     arr.getAndIncrement(k % arr.length());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[10];
        for (int k = 0; k < 10; k++) {
            ts[k] = new Thread(new AddThread());
        }
        for (int k = 0; k < 10; k++) {
            ts[k].start();
        }
        for (int k = 0; k < 10; k++) {
            ts[k].join();
        }
        System.out.println(arr);
    }
}

输出结果:

[10000,10000,10000,10000,10000,10000,10000,10000,10000,10000]

上述代码第2行,申明了一个内含10个元素的数组。第3行定义的线程对数组内10个元素进行累加操作,每个元素各加1000次。第11行,开启10个这样的线程。因此,可以预测,如果线程安全,数组内10个元素的值必然都是10000。反之,如果线程不安全,则部分或者全部数值会小于10000。

● AtomicLong、AtomicLongArray原子性整型数组

AtomicLong、AtomicLongArray的API跟AtomicInteger、AtomicIntegerArray在使用方法都是差不多的。区别在于用前者是使用原子方式更新的long值和long数组,后者是使用原子方式更新的Integer值和Integer数组。两者的相同处在于它们此类确实扩展了Number,允许那些处理基于数字类的工具和实用工具进行统一访问。在实际开发中,它们分别用于不同的场景。这个就具体情况具体分析了,下面将举例说明AtomicLong的使用场景(使用AtomicLong生成自增长ID)。

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

public class AtomicLongTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final AtomicLong orderIdGenerator = new AtomicLong(0);
        final List<Item> orders = Collections
                .synchronizedList(new ArrayList<Item>());
        for (int i = 0; i < 10; i++) {
            Thread orderCreationThread = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        long orderId = orderIdGenerator.incrementAndGet();
                        Item order = new Item(Thread.currentThread().getName(), orderId);
                        orders.add(order);
                    }
                }
            });
            orderCreationThread.setName("Order Creation Thread " + i);
            orderCreationThread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Set<Long> orderIds = new HashSet<Long>();
        for (Item order : orders) {
            orderIds.add(order.getID());
            System.out.println("Order name:" + order.getItemName()
                    + "----" + "Order id:" + order.getID());
        }
    }
}

class Item {
    String itemName;
    long id;

    Item(String n, long id) {
        this.itemName = n;
        this.id = id;
    }

    public String getItemName() {
        return itemName;
    }

    public long getID() {
        return id;
    }
}

输出:

Order name:Order Creation Thread 0----Order id:1

Order name:Order Creation Thread 1----Order id:2

Order name:Order Creation Thread 0----Order id:4

Order name:Order Creation Thread 1----Order id:5

Order name:Order Creation Thread 3----Order id:3

Order name:Order Creation Thread 0----Order id:7

Order name:Order Creation Thread 1----Order id:6

........

Order name:Order Creation Thread 2----Order id:100

从运行结果我们看到,不管是哪个线程。它们获得的ID是不会重复的,保证的ID生成的原子性,避免了线程安全上的问题。