● java.util.concurrent.atomic包
● AtomicBoolean原子性布爾
AtomicBoolean是java.util.concurrent.atomic包下的原子變量,這個(gè)包里面提供了一組原子類(lèi)。其基本的特性就是在多線程環(huán)境下,當(dāng)有多個(gè)線程同時(shí)執(zhí)行這些類(lèi)的實(shí)例包含的方法時(shí),具有排它性,即當(dāng)某個(gè)線程進(jìn)入方法,執(zhí)行其中的指令時(shí),不會(huì)被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執(zhí)行完成,才由JVM從等待隊(duì)列中選擇一個(gè)另一個(gè)線程進(jìn)入,這只是一種邏輯上的理解。實(shí)際上是借助硬件的相關(guān)指令來(lái)實(shí)現(xiàn)的,不會(huì)阻塞線程(或者說(shuō)只是在硬件級(jí)別上阻塞了)。
AtomicBoolean,在這個(gè)Boolean值的變化的時(shí)候不允許在之間插入,保持操作的原子性。下面將解釋重點(diǎn)方法并舉例:
boolean compareAndSet(expectedValue, updateValue);
● 這個(gè)方法主要有兩個(gè)作用:
比較AtomicBoolean和expect的值,如果一致,執(zhí)行方法內(nèi)的語(yǔ)句。其實(shí)就是一個(gè)if語(yǔ)句。
把AtomicBoolean的值設(shè)成update,比較最要的是這兩件事是一氣呵成的,這連個(gè)動(dòng)作之間不會(huì)被打斷,任何內(nèi)部或者外部的語(yǔ)句都不可能在兩個(gè)動(dòng)作之間運(yùn)行。為多線程的控制提供了解決的方案。
● 下面我們從代碼上解釋?zhuān)?/span>
首先我們看下在不使用 AtomicBoolean 情況下,代碼的運(yùn)行情況:
package com.bjpowernode;
import java.util.concurrent.TimeUnit;
public class BarWorker implements Runnable {
//靜態(tài)變量
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();
}
}
運(yùn)行結(jié)果:
bar1 enter
bar2 enter
bar1 working
bar2 working
bar1 leave
bar2 leave
從上面的運(yùn)行結(jié)果我們可看到,兩個(gè)線程運(yùn)行時(shí),都對(duì)靜態(tài)變量exists同時(shí)做操作,并沒(méi)有保證exists靜態(tài)變量的原子性,也就是一個(gè)線程在對(duì)靜態(tài)變量exists進(jìn)行操作到時(shí)候,其他線程必須等待或不作為。等待一個(gè)線程操作完后,才能對(duì)其進(jìn)行操作。
下面我們將靜態(tài)變量使用AtomicBoolean來(lái)進(jìn)行操作。
package com.bjpowernode;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class BarWorker2 implements Runnable {
//靜態(tài)變量使用 AtomicBoolean 進(jìn)行操作
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();
}
}
運(yùn)行結(jié)果:
bar1 enter
bar1 working
bar2 give up
bar1 leave
可以從上面的運(yùn)行結(jié)果看出僅僅一個(gè)線程進(jìn)行工作,因?yàn)閑xists.compareAndSet(false,true)提供了原子性操作,比較和賦值操作組成了一個(gè)原子操作,中間不會(huì)提供可乘之機(jī)。使得一個(gè)線程操作,其他線程等待或不作為。
下面我們簡(jiǎn)單介紹下AtomicBoolean的API
● 你可以這樣創(chuàng)建一個(gè)AtomicBoolean:
AtomicBoolean atomicBoolean = new AtomicBoolean();
以上示例新建了一個(gè)默認(rèn)值為false的AtomicBoolean。如果你想要為AtomicBoolean實(shí)例設(shè)置一個(gè)顯式的初始值,那么你可以將初始值傳給AtomicBoolean的構(gòu)造子:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
● 獲得AtomicBoolean的值:
你可以通過(guò)使用get()方法來(lái)獲取一個(gè)AtomicBoolean的值。示例如下:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean value = atomicBoolean.get();
● 設(shè)置AtomicBoolean的值:
你可以通過(guò)使用set()方法來(lái)設(shè)置一個(gè)AtomicBoolean的值。示例如下:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
atomicBoolean.set(false);
以上代碼執(zhí)行后AtomicBoolean的值為false。
● 交換AtomicBoolean的值:
你可以通過(guò) getAndSet()方法來(lái)交換一個(gè)AtomicBoolean實(shí)例的值。getAndSet()方法將返回AtomicBoolean當(dāng)前的值,并將為AtomicBoolean設(shè)置一個(gè)新值。示例如下:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean oldValue = atomicBoolean.getAndSet(false);
以上代碼執(zhí)行后oldValue變量的值為true,atomicBoolean實(shí)例將持有false值。代碼成功將AtomicBoolean當(dāng)前值ture交換為false。
● 比較并設(shè)置 AtomicBoolean 的值:
compareAndSet()方法允許你對(duì)AtomicBoolean的當(dāng)前值與一個(gè)期望值進(jìn)行比較,如果當(dāng)前值等于期望值的話,將會(huì)對(duì)AtomicBoolean設(shè)定一個(gè)新值。compareAndSet()方法是原子性的,因此在同一時(shí)間之內(nèi)有單個(gè)線程執(zhí)行它。因此compareAndSet()方法可被用于一些類(lèi)似于鎖的同步的簡(jiǎn)單實(shí)現(xiàn)。以下是一個(gè)compareAndSet()示例:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean expectedValue = true; boolean newValue = false;
boolean wasNewValueSet = atomicBoolean.compareAndSet(expectedValue, newValue);
本示例對(duì)AtomicBoolean的當(dāng)前值與true值進(jìn)行比較,如果相等,將AtomicBoolean的值更新為false。
● AtomicInteger原子性整型
AtomicInteger,一個(gè)提供原子操作的Integer的類(lèi)。在Java語(yǔ)言中,++i和i++操作并不是線程安全的,在使用的時(shí)候,不可避免的會(huì)用到synchronized關(guān)鍵字。而AtomicInteger則通過(guò)一種線程安全的加減操作接口。
我們先來(lái)看看AtomicInteger給我們提供了什么方法:
ublic final int get() //獲取當(dāng)前的值
public final int getAndSet(int newValue)//獲取當(dāng)前的值,并設(shè)置新的值
public final int getAndIncrement()//獲取當(dāng)前的值,并自增
public final int getAndDecrement() //獲取當(dāng)前的值,并自減
public final int getAndAdd(int delta) //獲取當(dāng)前的值,并加上預(yù)期的值
下面通過(guò)兩個(gè)簡(jiǎn)單的例子來(lái)看一下AtomicInteger的優(yōu)勢(shì)在哪?
● 普通線程同步:
class Test2 {
private volatile int count = 0;
public synchronized void increment() {
count++; //若要線程安全執(zhí)行執(zhí)行 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 之后,不需要加鎖,也可以實(shí)現(xiàn)線程安全。
public int getCount() {
return count.get();
}
}
從上面的例子中我們可以看出:使用AtomicInteger是非常安全的,而且因?yàn)锳tomicInteger由硬件提供原子操作指令實(shí)現(xiàn)的,在非激烈競(jìng)爭(zhēng)的情況下,開(kāi)銷(xiāo)更小,速度更快。AtomicInteger是使用非阻塞算法來(lái)實(shí)現(xiàn)并發(fā)控制的。AtomicInteger的關(guān)鍵域只有以下3個(gè):
// 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提供的獲得對(duì)對(duì)象內(nèi)存地址訪問(wèn)的類(lèi),注釋已經(jīng)清楚的寫(xiě)出了,它的作用就是在更新操作時(shí)提供“比較并替換”的作用。實(shí)際上就是AtomicInteger中的一個(gè)工具。valueOffset是用來(lái)記錄value本身在內(nèi)存的偏移地址的,這個(gè)記錄也主要是為了在更新操作在內(nèi)存中找到value的位置,方便比較。
注意:value是用來(lái)存儲(chǔ)整數(shù)的時(shí)間變量,這里被聲明為volatile,就是為了保證在更新操作時(shí),當(dāng)前線程可以拿到value最新的值(并發(fā)環(huán)境下,value可能已經(jīng)被其他線程更新了)。
優(yōu)點(diǎn):最大的好處就是可以避免多線程的優(yōu)先級(jí)倒置和死鎖情況的發(fā)生,提升在高并發(fā)處理下的性能。
● 下面我們簡(jiǎn)單介紹下 AtomicInteger 的 API
● 創(chuàng)建一個(gè) AtomicInteger 示例如下:
AtomicInteger atomicInteger = new AtomicInteger();
本示例將創(chuàng)建一個(gè)初始值為0的AtomicInteger。如果你想要?jiǎng)?chuàng)建一個(gè)給
定初始值的AtomicInteger,你可以這樣:
AtomicInteger atomicInteger = new AtomicInteger(123);
本示例將123作為參數(shù)傳給AtomicInteger的構(gòu)造子,它將設(shè)置AtomicInteger實(shí)例的初始值為123。
● 獲得AtomicInteger的值
你可以使用get()方法獲取AtomicInteger實(shí)例的值。示例如下:
AtomicInteger atomicInteger = new AtomicInteger(123);
int theValue = atomicInteger.get();
● 設(shè)置AtomicInteger的值
你可以通過(guò)set()方法對(duì)AtomicInteger的值進(jìn)行重新設(shè)置。以下是AtomicInteger.set()示例:
AtomicInteger atomicInteger = new AtomicInteger(123);
atomicInteger.set(234);
以上示例創(chuàng)建了一個(gè)初始值為123的AtomicInteger,而在第二行將其值更新為234。
● 比較并設(shè)置AtomicInteger的值
AtomicInteger類(lèi)也通過(guò)了一個(gè)原子性的compareAndSet()方法。這一方法將AtomicInteger實(shí)例的當(dāng)前值與期望值進(jìn)行比較,如果二者相等,為AtomicInteger實(shí)例設(shè)置一個(gè)新值。 AtomicInteger.compareAndSet()代碼示例:
AtomicInteger atomicInteger = new AtomicInteger(123);
int expectedValue = 123;
int newValue = 234;
atomicInteger.compareAndSet(expectedValue,newValue);
本示例首先新建一個(gè)初始值為123的AtomicInteger實(shí)例。然后將AtomicInteger與期望值123進(jìn)行比較,如果相等,將AtomicInteger的值更新為234。
● 增加AtomicInteger的值
AtomicInteger類(lèi)包含有一些方法,通過(guò)它們你可以增加AtomicInteger的值,并獲取其值。這些方法如下:
public final int addAndGet(int addValue)//在原來(lái)的數(shù)值上增加新的值,并返回新值
public final int getAndIncrement()//獲取當(dāng)前的值,并自增
public final int incrementAndget() //自減,并獲得自減后的值
public final int getAndAdd(int delta) //獲取當(dāng)前的值,并加上預(yù)期的值
第一個(gè)addAndGet()方法給AtomicInteger增加了一個(gè)值,然后返回增加后的值。getAndAdd()方法為AtomicInteger增加了一個(gè)值,但返回的是增加以前的AtomicInteger的值。具體使用哪一個(gè)取決于你的應(yīng)用場(chǎng)景。以下是這兩種方法的示例:
AtomicInteger atomicInteger = new AtomicInteger();
System.out.println(atomicInteger.getAndAdd(10));
System.out.println(atomicInteger.addAndGet(10));
本示例將打印出0和20。例子中,第二行拿到的是加10之前的AtomicInteger的值。加10之前的值是0。第三行將AtomicInteger的值再加10,并返回加操作之后的值。該值現(xiàn)在是為20。你當(dāng)然也可以使用這倆方法為AtomicInteger添加負(fù)值。結(jié)果實(shí)際是一個(gè)減法操作。getAndIncrement()和incrementAndGet()方法類(lèi)似于getAndAdd()和addAndGet(),但每次只將AtomicInteger的值加1。
● 減小AtomicInteger的值
AtomicInteger類(lèi)還提供了一些減小AtomicInteger的值的原子性方法。這些方法是:
public final int decrementAndGet()
public final int getAndDecrement()
decrementAndGet()將AtomicInteger的值減一,并返回減一后的值。getAndDecrement()也將AtomicInteger的值減一,但它返回的是減一之前的值。
● AtomicIntegerArray原子性整型數(shù)組
java.util.concurrent.atomic.AtomicIntegerArray類(lèi)提供了可以以原子方式讀取和寫(xiě)入的底層int數(shù)組的操作,還包含高級(jí)原子操作。AtomicIntegerArray支持對(duì)底層int數(shù)組變量的原子操作。它具有獲取和設(shè)置方法,如在變量上的讀取和寫(xiě)入。也就是說(shuō),一個(gè)集合與同一變量上的任何后續(xù)get相關(guān)聯(lián)。原子compareAndSet方法也具有這些內(nèi)存一致性功能。
AtomicIntegerArray本質(zhì)上是對(duì)int[]類(lèi)型的封裝。使用Unsafe類(lèi)通過(guò)CAS的方式控制int[]在多線程下的安全性。它提供了以下幾個(gè)核心API:
//獲得數(shù)組第 i 個(gè)下標(biāo)的元素
public final int get(int i)
//獲得數(shù)組的長(zhǎng)度
public final int length()
//將數(shù)組第 i 個(gè)下標(biāo)設(shè)置為 newValue,并返回舊的值
public final int getAndSet(int i, int newValue)
//進(jìn)行 CAS 操作,如果第 i 個(gè)下標(biāo)的元素等于 expect,則設(shè)置為 update,設(shè)置成功返回 true
public final boolean compareAndSet(int i, int expect, int update)
//將第 i 個(gè)下標(biāo)的元素加 1
public final int getAndIncrement(int i)
//將第 i 個(gè)下標(biāo)的元素減 1
public final int getAndDecrement(int i)
//將第 i 個(gè)下標(biāo)的元素增加 delta(delta 可以是負(fù)數(shù))
public final int getAndAdd(int i,int delta)
下面給出一個(gè)簡(jiǎn)單的示例,展示 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);
}
}
輸出結(jié)果:
[10000,10000,10000,10000,10000,10000,10000,10000,10000,10000]
上述代碼第2行,申明了一個(gè)內(nèi)含10個(gè)元素的數(shù)組。第3行定義的線程對(duì)數(shù)組內(nèi)10個(gè)元素進(jìn)行累加操作,每個(gè)元素各加1000次。第11行,開(kāi)啟10個(gè)這樣的線程。因此,可以預(yù)測(cè),如果線程安全,數(shù)組內(nèi)10個(gè)元素的值必然都是10000。反之,如果線程不安全,則部分或者全部數(shù)值會(huì)小于10000。
● AtomicLong、AtomicLongArray原子性整型數(shù)組
AtomicLong、AtomicLongArray的API跟AtomicInteger、AtomicIntegerArray在使用方法都是差不多的。區(qū)別在于用前者是使用原子方式更新的long值和long數(shù)組,后者是使用原子方式更新的Integer值和Integer數(shù)組。兩者的相同處在于它們此類(lèi)確實(shí)擴(kuò)展了Number,允許那些處理基于數(shù)字類(lèi)的工具和實(shí)用工具進(jìn)行統(tǒng)一訪問(wèn)。在實(shí)際開(kāi)發(fā)中,它們分別用于不同的場(chǎng)景。這個(gè)就具體情況具體分析了,下面將舉例說(shuō)明AtomicLong的使用場(chǎng)景(使用AtomicLong生成自增長(zhǎng)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
從運(yùn)行結(jié)果我們看到,不管是哪個(gè)線程。它們獲得的ID是不會(huì)重復(fù)的,保證的ID生成的原子性,避免了線程安全上的問(wèn)題。