和朱曄一起復習Java併發(四):Atomic
本節我們來研究下併發包中的Atomic型別。
AtomicXXX和XXXAdder以及XXXAccumulator效能測試
先來一把效能測試,對比一下AtomicLong(1.5出來的)、LongAdder(1.8出來的)和LongAccumulator(1.8出來的)用於簡單累加的效能。
程式邏輯比較簡單,可以看到我們在最大併發10的情況下,去做10億次操作測試:
@Slf4j public class AccumulatorBenchmark { private StopWatch stopWatch = new StopWatch(); static final int threadCount = 100; static final int taskCount = 1000000000; static final AtomicLong atomicLong = new AtomicLong(); static final LongAdder longAdder = new LongAdder(); static final LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0L); @Test public void test() { Map<String, IntConsumer> tasks = new HashMap<>(); tasks.put("atomicLong", i -> atomicLong.incrementAndGet()); tasks.put("longAdder", i -> longAdder.increment()); tasks.put("longAccumulator", i -> longAccumulator.accumulate(1L)); tasks.entrySet().forEach(item -> benchmark(threadCount, taskCount, item.getValue(), item.getKey())); log.info(stopWatch.prettyPrint()); Assert.assertEquals(taskCount, atomicLong.get()); Assert.assertEquals(taskCount, longAdder.longValue()); Assert.assertEquals(taskCount, longAccumulator.longValue()); } private void benchmark(int threadCount, int taskCount, IntConsumer task, String name) { stopWatch.start(name); ForkJoinPool forkJoinPool = new ForkJoinPool(threadCount); forkJoinPool.execute(() -> IntStream.rangeClosed(1, taskCount).parallel().forEach(task)); forkJoinPool.shutdown(); try { forkJoinPool.awaitTermination(1, TimeUnit.HOURS); } catch (InterruptedException e) { e.printStackTrace(); } stopWatch.stop(); } }
結果如下:
和官網說的差不多,在高併發的情況下LongAdder效能會比AtomicLong好很多。
AtomicReference可見性問題測試
在很多開原始碼中我們有看到AtomicReference的身影,它究竟是幹什麼的呢?我們來寫一段測試程式,在這個程式中我們定一了一個Switch型別,作為一個開關,然後寫三個死迴圈的執行緒來測試,當開關有效的時候會持續死迴圈,在2秒後關閉所有的三個開關:
- 第一個是普通的Switch
- 第二個是使用了volatile宣告的Switch
- 第三個是AtomicReference包裝的Switch
@Slf4j public class AtomicReferenceTest { private Switch rawValue = new Switch(); private volatile Switch volatileValue = new Switch(); private AtomicReference<Switch> atomicValue = new AtomicReference<>(new Switch()); @Test public void test() throws InterruptedException { new Thread(() -> { log.info("Start:rawValue"); while (rawValue.get()) { } log.info("Done:rawValue"); }).start(); new Thread(() -> { log.info("Start:volatileValue"); while (volatileValue.get()) { } log.info("Done:volatileValue"); }).start(); new Thread(() -> { log.info("Start:atomicValue"); while (atomicValue.get().get()) { } log.info("Done:atomicValue"); }).start(); Executors.newSingleThreadScheduledExecutor().schedule(rawValue::off, 2, TimeUnit.SECONDS); Executors.newSingleThreadScheduledExecutor().schedule(volatileValue::off, 2, TimeUnit.SECONDS); Executors.newSingleThreadScheduledExecutor().schedule(atomicValue.get()::off, 2, TimeUnit.SECONDS); TimeUnit.HOURS.sleep(1); } class Switch { private boolean enable = true; public boolean get() { return enable; } public void off() { enable = false; } } }
執行程式:
可以看到2秒後有一個開關卡住了,執行緒沒有退出。這是一個可見性的問題,AtomicReference以及volatile可以確保執行緒對資料的更新重新整理到記憶體。因為我們對於開關的關閉是在另一個定時任務執行緒做的,如果我們不使用volatile或AtomicReference來定義物件,那麼物件的操作可能無法被其它執行緒感知到。當然,AtomicReference除了解決可見性問題還有更多AtomicXXX提供的其它功能。
AtomicInteger測試
下面我們來看一下AtomicInteger的compareAndSet()功能。首先說明這個程式沒有任何意義,只是測試一下功能。在這個程式裡,我們亂序開啟10個執行緒,每一個執行緒的任務就是按照次序來累加數字。我們使用AtomicInteger的compareAndSet()來確保亂序的執行緒也能按照我們要的順序操作累加。
@Slf4j
public class AtomicIntegerTest {
@Test
public void test() throws InterruptedException {
AtomicInteger atomicInteger = new AtomicInteger(0);
List<Thread> threadList = IntStream.range(0,10).mapToObj(i-> {
Thread thread = new Thread(() -> {
log.debug("Wait {}->{}", i, i+1);
while (!atomicInteger.compareAndSet(i, i + 1)) {
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("Done {}->{}", i, i+1);
});
thread.setName(UUID.randomUUID().toString());
return thread;
}).sorted(Comparator.comparing(Thread::getName)).collect(Collectors.toList());
for (Thread thread : threadList) {
thread.start();
}
for (Thread thread : threadList) {
thread.join();
}
log.info("result:{}", atomicInteger.get());
}
}
執行結果如下:
11:46:30.611 [2c80b367-d80e-46b5-94f5-b7b172e79dad] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 4->5
11:46:30.611 [7bccbb54-4573-4b77-979b-840613406428] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 5->6
11:46:30.612 [c0792831-6201-4f6c-b702-79c1b798c3aa] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 9->10
11:46:30.612 [949b0c26-febb-4830-ad98-f43521ce4382] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 7->8
11:46:30.613 [ccc05b0f-11da-41fa-b8fc-59a90dfc2250] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 6->7
11:46:30.611 [037e9595-73cb-4aa1-afee-4250347746c8] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 3->4
11:46:30.611 [4f15d9ce-044e-4657-b418-4874d03e5d22] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 1->2
11:46:30.611 [3a96c35c-bc4e-45f4-aae4-9fd8611acaea] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 8->9
11:46:30.611 [94465214-27bf-4543-80e2-dbaeeb6ddc94] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 0->1
11:46:30.611 [60f9cb50-21e6-45bc-9b4d-867783ab033b] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Wait 2->3
11:46:30.627 [94465214-27bf-4543-80e2-dbaeeb6ddc94] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 0->1
11:46:30.681 [4f15d9ce-044e-4657-b418-4874d03e5d22] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 1->2
11:46:30.681 [60f9cb50-21e6-45bc-9b4d-867783ab033b] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 2->3
11:46:30.734 [037e9595-73cb-4aa1-afee-4250347746c8] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 3->4
11:46:30.780 [2c80b367-d80e-46b5-94f5-b7b172e79dad] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 4->5
11:46:30.785 [7bccbb54-4573-4b77-979b-840613406428] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 5->6
11:46:30.785 [ccc05b0f-11da-41fa-b8fc-59a90dfc2250] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 6->7
11:46:30.787 [949b0c26-febb-4830-ad98-f43521ce4382] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 7->8
11:46:30.838 [3a96c35c-bc4e-45f4-aae4-9fd8611acaea] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 8->9
11:46:30.890 [c0792831-6201-4f6c-b702-79c1b798c3aa] DEBUG me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - Done 9->10
11:46:30.890 [main] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicIntegerTest - result:10
可以看到,Wait的輸出是亂序的,最後Done的輸出是順序的。
AtomicStampedReference測試
AtomicStampedReference可以用來解決ABA問題,什麼是ABA問題我們看這個例子:
執行緒1讀取了數字之後,等待1秒,然後嘗試把1修改為3。
執行緒2後啟動,讀取到數字1後修改2,稍等一下又修改回1。
雖然AtomicInteger確保多個執行緒的原子性操作,但是無法確保1就是原先讀取到的那個1,沒有經過別人修改。
可以再換一個例子來說,如果我們現在賬上有100元,要修改為200元,在修改之前賬戶已經被操作過了從100元充值到了150然後提現到了100,雖然最後還是回到了100,但是這個時候嚴格一點的話,我們應該認為這個100不是原先的100,這個賬戶的版本發生了變化,如果我們使用樂觀行鎖的話,雖然餘額都是100但是行鎖的版本肯定不一致,AtomicStampedReference就是類似行樂觀鎖的概念。
@Test
public void test() throws InterruptedException {
AtomicInteger atomicInteger = new AtomicInteger(1);
Thread thread1 = new Thread(() -> {
int value = atomicInteger.get();
log.info("thread 1 read value: " + value);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicInteger.compareAndSet(value, 3)) {
log.info("thread 1 update from " + value + " to 3");
} else {
log.info("thread 1 update fail!");
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
int value = atomicInteger.get();
log.info("thread 2 read value: " + value);
if (atomicInteger.compareAndSet(value, 2)) {
log.info("thread 2 update from " + value + " to 2");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = atomicInteger.get();
log.info("thread 2 read value: " + value);
if (atomicInteger.compareAndSet(value, 1)) {
log.info("thread 2 update from " + value + " to 1");
}
}
});
thread2.start();
thread1.join();
thread2.join();
}
看下執行結果:
11:56:20.373 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 read value: 1
11:56:20.381 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 update from 1 to 2
11:56:20.373 [Thread-0] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 1 read value: 1
11:56:20.483 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 read value: 2
11:56:20.484 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 update from 2 to 1
11:56:21.386 [Thread-0] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 1 update from 1 to 3
下面我們使用AtomicStampedReference來修復這個問題:
@Test
public void test2() throws InterruptedException {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
Thread thread1 = new Thread(() -> {
int[] stampHolder = new int[1];
int value = atomicStampedReference.get(stampHolder);
int stamp = stampHolder[0];
log.info("thread 1 read value: " + value + ", stamp: " + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicStampedReference.compareAndSet(value, 3, stamp, stamp + 1)) {
log.info("thread 1 update from " + value + " to 3");
} else {
log.info("thread 1 update fail!");
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
int[] stampHolder = new int[1];
int value = atomicStampedReference.get(stampHolder);
int stamp = stampHolder[0];
log.info("thread 2 read value: " + value + ", stamp: " + stamp);
if (atomicStampedReference.compareAndSet(value, 2, stamp, stamp + 1)) {
log.info("thread 2 update from " + value + " to 2");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = atomicStampedReference.get(stampHolder);
stamp = stampHolder[0];
log.info("thread 2 read value: " + value + ", stamp: " + stamp);
if (atomicStampedReference.compareAndSet(value, 1, stamp, stamp + 1)) {
log.info("thread 2 update from " + value + " to 1");
}
value = atomicStampedReference.get(stampHolder);
stamp = stampHolder[0];
log.info("thread 2 read value: " + value + ", stamp: " + stamp);
}
});
thread2.start();
thread1.join();
thread2.join();
}
執行結果如下:
11:59:11.946 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 read value: 1, stamp: 1
11:59:11.951 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 update from 1 to 2
11:59:11.946 [Thread-0] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 1 read value: 1, stamp: 1
11:59:12.053 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 read value: 2, stamp: 2
11:59:12.053 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 update from 2 to 1
11:59:12.053 [Thread-1] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 2 read value: 1, stamp: 3
11:59:12.954 [Thread-0] INFO me.josephzhu.javaconcurrenttest.atomic.AtomicStampedReferenceTest - thread 1 update fail!
可以看到,現在我們修改資料的時候不僅僅是拿著值來修改了,還要提供版本號,讀取資料的時候可以讀取到資料以及版本號。這樣的話,雖然數值不變,但是執行緒2經過兩次修改後資料的版本從1變為了3,回過頭來執行緒1再要拿著版本號1來修改資料的話必然失敗。
一個有趣的問題
本文比較短,我們再來看網友之前問的一個有意思的問題,程式如下。
@Slf4j
public class InterestingProblem {
int a = 1;
int b = 1;
void add() {
a++;
b++;
}
void compare() {
if (a < b)
log.info("a:{},b:{},{}", a, b, a>b);
}
@Test
public void test() throws InterruptedException {
new Thread(() -> {
while (true)
add();
}).start();
new Thread(() -> {
while (true)
compare();
}).start();
TimeUnit.MILLISECONDS.sleep(100);
}
}
這位網友是這麼問的,他說見鬼了,不但能看到日誌輸出,而且我發現之前判斷過一次a<b,之後輸出a>b居然是成立的,結果裡可以看到true,JVM出現Bug了可能:
他覺得a和b不是靜態的,為啥會出現併發問題呢於是問了同事:
- 同事A說是肯定多執行緒問題,加volatile可以解決,但是他發現為a和b加上volatile也不行
- 同事B說是AtomicInteger可以解決併發性問題,但是把a和b都用上了AtomicInteger也沒用
- 同事C貌似看出了問題說需要鎖,為add()方法增加synchronized關鍵字鎖一下,但是也沒用
這位網友其實是沒有搞清楚多執行緒情況下,可見性問題、原子性問題解決的事情,同事也把各種併發的概念混淆在一起了。
我們這麼來看這段程式碼,這段程式碼裡一個執行緒不斷操作a和b進行累加操作,一個執行緒判斷a和b,然後輸出結果。出現這個問題的原因本質上是因為a<b是三步操作,取a,取b以及比較,不是原子性的,在整個過程中可能穿插了add執行緒的操作a和b。如果先獲取a,然後a++ b++,然後獲取b,這個時候a<b,如果先a++,然後獲取a,獲取b,最後b++,這個時候a>b。我們來看一下compare()方法的位元組碼,可以很明顯看到ab的比較分明是4行指令,我們不能以程式碼行數來判斷操作是否是原子的,不是原子意味著操作過程中可能被穿插了其它執行緒的其它程式碼:
0 aload_0
1 getfield #2 <me/josephzhu/javaconcurrenttest/atomic/InterestingProblem.a>
4 aload_0
5 getfield #3 <me/josephzhu/javaconcurrenttest/atomic/InterestingProblem.b>
8 if_icmpge 67 (+59)
11 getstatic #4 <me/josephzhu/javaconcurrenttest/atomic/InterestingProblem.log>
14 ldc #5 <a:{},b:{},{}>
16 iconst_3
17 anewarray #6 <java/lang/Object>
20 dup
21 iconst_0
22 aload_0
23 getfield #2 <me/josephzhu/javaconcurrenttest/atomic/InterestingProblem.a>
26 invokestatic #7 <java/lang/Integer.valueOf>
29 aastore
30 dup
31 iconst_1
32 aload_0
33 getfield #3 <me/josephzhu/javaconcurrenttest/atomic/InterestingProblem.b>
36 invokestatic #7 <java/lang/Integer.valueOf>
39 aastore
40 dup
41 iconst_2
42 aload_0
43 getfield #2 <me/josephzhu/javaconcurrenttest/atomic/InterestingProblem.a>
46 aload_0
47 getfield #3 <me/josephzhu/javaconcurrenttest/atomic/InterestingProblem.b>
50 if_icmple 57 (+7)
53 iconst_1
54 goto 58 (+4)
57 iconst_0
58 invokestatic #8 <java/lang/Boolean.valueOf>
61 aastore
62 invokeinterface #9 <org/slf4j/Logger.info> count 3
67 return
所以這位網友的理解有幾個問題:
- 多執行緒操作的物件安全不安全和物件是否靜態沒關係,即使不是static的也可能會併發被多個執行緒來操作
- 不能根據程式碼行數或程式碼是否簡單來判斷程式碼是否原子的,別說Java程式碼了,就是位元組碼也不行
我們再來看看他三位同事的說法:
- 同事A可能沒有徹底理解volatile的作用,現在是兩個執行緒操作a和b相互交錯干擾,加上了volatile只會讓問題更嚴重(你可以寫一段程式碼對比下加上和不加上volatile最後出現true的概率),這不是可見性問題
- 同事B可能也沒仔細考慮AtomicInteger的作用,AtomicInteger是用來實現多執行緒情況下原子性操作Integer,現在並沒有多個執行緒來併發修改a和b,使用AtomicInteger不能解決問題
- 同事C貌似是看到了問題的所在,但是他也沒理清楚,add()方法僅僅只有一個執行緒在執行為這個方法加上鎖是沒有用的,現在的問題在於add()和compare()的干擾,它們需要序列執行才能確保a和b整體的完整
所以要進行簡單修復這個問題的話就是為add()和compare()都加上synchronized關鍵字,除了這個鎖的方式有沒有其它方式呢?你可以想想。
小結
本文簡單測試了一下java.util.concurrent.atomic包下面的一些常用Atomic操作類,最後分享了一個網友的問題和疑惑,希望文字對你有用。
同樣,程式碼見我的Github,歡迎clone後自己把玩,歡迎點贊。
歡迎關注我的微信公眾號:隨緣主人的園子
相關推薦
和朱曄一起復習Java併發(四):Atomic
本節我們來研究下併發包中的Atomic型別。 AtomicXXX和XXXAdder以及XXXAccumulator效能測試 先來一把效能測試,對比一下AtomicLong(1.5出來的)、LongAdder(1.8出來的)和LongAccumulator(1.8出來的)用於簡單累加的效能。 程式邏輯比較簡單,
和朱曄一起復習Java併發(一):執行緒池
和我之前的Spring系列文章一樣,我們會以做一些Demo做實驗的方式來複習一些知識點。 本文我們先從Java併發中最最常用的執行緒池開始。 從一個執行緒池實驗開始 首先我們寫一個方法來每秒一次定時輸出執行緒池的基本資訊: private void printStats(ThreadPoolExecutor
和朱曄一起復習Java併發(二):佇列
和朱曄一起復習Java併發(二):佇列 老樣子,我們還是從一些例子開始慢慢熟悉各種併發佇列。以看小說看故事的心態來學習不會顯得那麼枯燥而且更容易記憶深刻。 阻塞佇列的等待? 阻塞佇列最適合做的事情就是做為生產消費者的中間儲存,以抵抗生產者消費者速率不匹配的問題,不但是在速率不匹配的時候能夠有地方暫存任務,而且
和朱曄一起復習Java併發(三):鎖(含鎖效能測試)
這個專題我發現怎麼慢慢演化為效能測試了,遇到任何東西我就忍不住去測一把。本文我們會大概看一下各種鎖資料結構的簡單用法,順便也會來比拼一下效能。 各種併發鎖 首先,我們定一個抽象基類,用於各種鎖測試的一些公共程式碼: 我們需要使用鎖來保護counter和hashMap這2個資源 write欄位表示這個執行緒是
和朱曄一起復習Java併發(五):併發容器和同步器
本節我們先會來複習一下java.util.concurrent下面的一些併發容器,然後再會來簡單看一下各種同步器。 ConcurrentHashMap和ConcurrentSkipListMap的效能 首先,我們來測試一下ConcurrentHashMap和ConcurrentSkipListMap的效能。
Java併發(四):volatile的實現原理 Java併發(一):Java記憶體模型乾貨總結
synchronized是一個重量級的鎖,volatile通常被比喻成輕量級的synchronized volatile是一個變數修飾符,只能用來修飾變數。 volatile寫:當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體。 volatile讀:當讀一
Java併發(四):happens-before
happens-before 一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係 happen-before原則是JMM中非常重要的原則,它是判斷資料是否存在競爭、執行緒是否安全的主要依據,保證了多執行緒環境下的可見性。 happens-before原則定
Java併發(四):volatile的實現原理
synchronized是一個重量級的鎖,volatile通常被比喻成輕量級的synchronized volatile是一個變數修飾符,只能用來修飾變數。 volatile寫:當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體。 volatile讀:當讀一個vo
Java併發(三):synchronized實現原理
一、synchronized用法 Java中的同步塊用synchronized標記。 同步塊在Java中是同步在某個物件上(監視器物件)。 所有同步在一個物件上的同步塊在同時只能被一個執行緒進入並執行操作。 所有其他等待進入該同步塊的執行緒將被阻塞,直到執行該同步塊中的執行緒退出。 (注:不要使用全
Java併發(三):重排序
在執行程式時為了提高效能,提高並行度,編譯器和處理器常常會對指令做重排序。重排序分三種類型: 編譯器優化的重排序。編譯器在不改變單執行緒程式語義的前提下,可以重新安排語句的執行順序。 指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-Level Parallelism,
Java併發(七):雙重檢驗鎖定DCL Java併發(六):volatile的實現原理
雙重檢查鎖定(Double Check Lock,DCL) 1、懶漢式單例模式,無法保證執行緒安全: public class Singleton { private static Singleton singleton; private Singleton
Java併發(十三):同步屏障CyclicBarrier
CyclicBarrier 的字面意思是可迴圈使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續幹活。 一、應用舉例 public class CyclicBar
Java併發(七):AbstractQueuedSynchronizer
AbstractQueuedSynchronizer(AQS)這個抽象類,是Java併發包 java.util.concurrent 的基礎工具類,是實現 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等類的基礎。 一、CLH同步佇列 AQS通過內建的FI
設計模式(一):單例模式 JVM類載入機制 JDK原始碼學習筆記——Enum列舉使用及原理 Java併發(七):雙重檢驗鎖定DCL Java併發(二):Java記憶體模型 Java併發(二):Java記憶體模型 Java併發(七):雙重檢驗鎖定DCL JDK原始碼學習筆記——Enum列舉使用及原理
單例模式是一種常用的軟體設計模式,其定義是單例物件的類只能允許一個例項存在。 單例模式一般體現在類宣告中,單例的類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。 適用場合: 需要頻繁的進行建立和銷燬的物件; 建立物
菜鳥之路——Java併發(四)執行緒池的使用
轉自:http://www.cnblogs.com/dolphin0520/p/3932921.html 在前面的文章中,我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便,但是就會有一個問題: 如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間
Java核心(四):Java中的裝箱和拆箱
https 語言 log .cn 實現 技術 沒有 object 面向 一、為什麽需要裝箱和拆箱? 基礎數據類型是數據,不是對象,也不是Object的子類。Java為每種基本數據類型都提供了對應的包裝器類型。裝箱就是自動將基本數據類型轉換為包裝器類型;拆箱就是自動將包
Java-Maven(四):Eclipse集成Maven環境配置
查找 epo 情況 jpg ont 使用 eclipse版 需要 style 一般maven都需要集成到IDE上使用的,而不是單獨的使用,常見的maven可集成IDE:eclipse、IntelliJ IDEA。但這裏就只學習eclipse集成maven的基礎上,進行mav
Java容器(四):HashMap(Java 7)的實現原理
一、HashMap的定義和建構函式 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, S
java基礎(四):字串
1.String概念:字串一旦被建立就不可以更改 (1)A.==B:判斷地址; A.equals(B):判斷A,B的內容 (2).常用方法: 長度:str.length();
Java入門(四):運算子優先順序、關鍵字與保留字
上次介紹了Java的運算子,今天來介紹下運算子的優先順序,以及Java的關鍵字、保留字。 一、運算子優先順序 序號 運算子 名稱 目數 結合性 說明 1 [ ]