主要參考《實戰java高併發程序設計》、《併發編程的藝術》,感覺學習這13個原子類,還是要自己動手寫一些,試一試,才能理解它們的用法和不同之處。
1.原子類的引入以及AtomicInteger
首先看一個例子:
public class AtomicNoIntegerDemo {
static int i = 0;
public static class AddThread implements Runnable{
@Override
public void run(){
for(int k=0;k<10000;k++)
i++;
}
}
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(i);
}
}
一個普通的int變量,以上面的代碼相加,最後的值可能不是100000,因爲10個線程共同對i做加法,但是i++的過程不是原子操作,會出現線程不安全的可能。可以按照下面的方法修改,將i++加鎖,每次只能有一個線程進行i++,這樣就能保證最終i的值爲100000。
public void run(){
for(int k=0;k<10000;k++)
synchronized(AtomicNoIntegerDemo.class) {
i++;
}
}
這樣做是使用加鎖的方式,這樣做效率會比較低,而無鎖是一種樂觀的策略,它使用CAS(Compare And Swap)策略來鑑別線程衝突,一旦檢測到衝突產生,就重試當前操作直到沒有衝突爲止。
CAS優勢:
CAS:算法過程:
而原子類就是使用CAS來實現的,JDK有一個併發包atomic,裏面實現了一些直接使用CAS操作的線程安全的類型。上面那個例子可以使用AtomicTnteger來保證線程安全。
代碼如下,很好理解
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicNoIntegerDemo {
static AtomicInteger i = new AtomicInteger();
public static class AddThread implements Runnable{
@Override
public void run(){
for(int k=0;k<10000;k++)
i.incrementAndGet();
}
}
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(i);
}
}
2.無鎖的對象引用:AtomicReference
上面講述了AtomicInteger的用法,但如果一個對象,比如Student類的一個對象student,想要原子的更新等,那麼就不能用AtomicInteger了,這時候可以使用AtomicReference,利用泛型,實現各種類對象的原子操作,保證線程安全。一個例子如下所示。
public class JustTry {
public static void main(String[] args) throws InterruptedException {
//可以在定義student1時傳入一個Student對象作爲參數,也可以在下面的set時設置student1的值
AtomicReference<Student> student1 = new AtomicReference<>();
Student s1 = new Student("Tom",19);
Student s2 = new Student("Tony",20);
student1.set(s1);
while (true){
if(student1.compareAndSet(s1,s2)) break;
}
System.out.println(student1.get().getName());
//可以看到student1的改變沒有影響s1
System.out.println(s1.getName());
}
public static class Student{
private int age;
private String name;
Student(String name,int age){
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
AtomicReference有一個問題,如果在CAS過程中,其他線程改變了這個變量,又改回去了,那麼CAS會照常進行,沒有反映,這在一些情況下沒有影響,但在一些情況下可能會造成很大的問題,所以需要一回人標誌位判斷這個變量是否改變過,這時AtomicStampedReference可以排上用場,注意,AtomicStampedReference定義時需要傳入泛型類的對象來初始化,CAS時要判斷標誌位是否變化,其他和AtomicReference區別不大。一個例子:
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo {
static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
final int timestamp = money.getStamp();
new Thread() {
public void run() {
while (true) {
while (true) {
Integer m = money.getReference();
if (m < 20) {
if (money.compareAndSet(m, m + 20, timestamp, timestamp + 1)) {
System.out.println("餘額小於20元,充值成功,餘額:" + money.getReference() + "元");
break;
}
} else {
// System.out.println("餘額大於20元,無需充值");
break;
}
}
}
}
}.start();
}
new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
while (true) {
int timestamp = money.getStamp();
Integer m = money.getReference();
if (m > 10) {
System.out.println("大於10元");
if (money.compareAndSet(m, m - 10, timestamp, timestamp + 1)) {
System.out.println("成功消費10元,餘額:" + money.getReference());
break;
}
} else {
System.out.println("沒有足夠的金額");
break;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
3.無鎖的數組 AtomicIntegerArray
無鎖的數組可以使用AtomicIntegerArray,可以使用這個類提供的方法,線程安全地對數組中的元素進行操作。
mport java.util.concurrent.atomic.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);
}
}
4. 對象中某個字段的原子操作:AtomicIntegerFieldUpdater
上面的AtomicReference只能保證原子地操作對象本身,而AtomicIntegerFieldUpdater可以原子地操作對象中的字段。下面代碼中Candidate,score和allSore總是相等,說明AtomicIntegerFieldUpdater保證了原子操作和線程安全。
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
public static class Candidate {
int id;
AtomicInteger score = new AtomicInteger(0);
}
public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
public static AtomicInteger allscore = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate stu = new Candidate();
Thread[] t = new Thread[10000];
for (int i = 0; i < 10000; i++) {
t[i] = new Thread() {
public void run() {
if (Math.random() > 0.4) {
scoreUpdater.incrementAndGet(stu);
allscore.incrementAndGet();
}
}
};
t[i].start();
}
for (int i = 0; i < 10000; i++) {
t[i].join();
}
System.out.println("score=" + stu.score);
System.out.println("allscore=" + allscore);
}
}
注意點:
: