一、JUC-TimeUnit枚舉
TimeUnit是 java.util.concurrent 中的一個枚舉類(時間單元類)。一般讓進行控制線程睡眠時使用。
TimeUnit提供了可讀性更好的線程暫停操作,通常用來替換Thread.sleep(),相比 Thread.sleep()方法的一個好處就是 TimeUnit可以設置時間單位。
這個類支持有:日(DAYS)、時(HOURS)、分(MINUTS)、秒(SECONDS)、毫秒(MILLISECONDS)、微秒(MICROSECONDS)、納秒(NANOSECONDS)。
1、實例:進行休眠控制,休眠2秒
使用Thread.sleep() 方法處理
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("[sleep start]"+System.currentTimeMillis());
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[sleep end]"+System.currentTimeMillis());
}
}).start();
}
直接使用TimeUnit類來處理
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("[sleep start]"+System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[sleep end]"+System.currentTimeMillis());
}
}).start();
}
2、時間單位的轉換
在 TimeUnit類中最爲重要的特點是可以方便的進行各種時間單位的轉換,它提供了一個convert()方法
實例:1小時轉換爲秒數
public static void main(String[] args) {
long sec = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS);
System.out.println("1小時轉換爲秒數:" + sec);
long minutes = TimeUnit.MINUTES.convert(sec, TimeUnit.SECONDS);
System.out.println(sec + "秒有轉換爲分鐘:" + minutes);
}
二、JUC-Atomic Variables(原子變量)
簡單使用:volatile關鍵字與內存可見性
三、JUC-Concurrent Collections(併發容器)
JUC裏的同步容器類
1、java 基礎數據集合容器中
線程安全與非線程安全的對象如下
Collection |--List |--ArrayList
| |--LinkList
| |--Vector -->線程安全
|
|
| --Set |--TreeSet
|--HashSet
Map |--TreeMap
|--HashMap
|--HashTable -->線程安全
StringBuffer -->線程安全
StringBulider -->非線程安全
解決這些非線程安全的集合的線程安全
通過使用的 Collections.synchronizedXXX()方法來轉換。HashMap的話可以直接使用HashTable轉換
(1)List用線程安全對象處理,map同理
public class CollectionDemo {
private static Vector list = new Vector();
// private static List<Integer> list = new ArrayList();
// private static List<Integer> list = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args){
//讓50個線程去執行
for (int i = 0; i < 50; i++) {
new MyThread().start();
}
}
private static class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
list.add(i);
}
System.out.println("集合大小:--->"+list.size());//預期的集合大小應該是50x100=5000
}
}
}
(2)讀寫處理
public class CollectionDemo {
private static List<Object> list = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new MyThread().start();
}
}
private static class MyThread extends Thread{
@Override
public void run() {
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
list.add("add" + Thread.currentThread().getName());
}
}
}
注意:上面的解決方法,一是程序效率低;二是在複合操作的時候會報併發修改異常(ConcurrentModificationException)。
2、JUC 解決非線程安全的集合類
JUC裏面提供了一系列同步容器類用來解決非線程安全的集合類,我們只需要在多線程併發編程中,用這些同步容器類類替換掉原來的HashMap,ArrayList,HashSet集合,就可以了保證即是線程安全的,比使用 Collections.synchronizedXXX()的效率高。
HashMap -- ConcurrentHashMap
TreeMap -- ConcurrentSkipListMap
ArrayList -- CopyOnWriteArrayList
ArraySet -- CopyOnWriteArraySet等等
CopyOnWriteArrayList和CopyOnWriteArraySet 每次存入要新建一個存儲結構,寫操作效率低比Vector都低,浪費空間,用於寫得少,讀得多。
public class CollectionDemo {
private static CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();
public static void main(String[] args){
for (int i = 0; i < 5; i++) {
new MyThread().start();
}
}
private static class MyThread extends Thread{
@Override
public void run() {
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(Thread.currentThread().getName() + "==" + iterator.next());
}
list.add("add" + Thread.currentThread().getName());
}
}
}
四、JUC-Synchronizers(同步器)
簡單瞭解三個類用於同步一批線程的行爲,分別是CountDownLatch、Semaphore和CyclicBarrier。
1、CountDownLatch類
CountDownLatch是一個計數器閉鎖,它允許一個或多個線程等待直到在其他線程中一組操作執行完成。CountDownLatch用一個給定的計數器來初始化,該計數器的操作是原子操作。
例如:爲了讓主線程等待工作線程執行完成,主線程調用await操作讓主線程阻塞,當工作線程完成初始化過程之後,每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。
public class CountDownLatchDemo {
public static void main(String[] args) {
// 創建 CountDownLatch, 3個線程任務
CountDownLatch countDownLatch = new CountDownLatch(3);
LatchDemo latchDemo = new LatchDemo(countDownLatch);
for (int i = 0; i < 3; i++) {
new Thread(latchDemo).start();
}
try {
// 主線程執行await方法,進行等待,知道計數器的值爲0時繼續向下執行
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程運行完成");
}
public static class LatchDemo implements Runnable{
private CountDownLatch countDownLatch = null;
public LatchDemo(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
for (int i = 0; i <5; i++) {
System.out.println(Thread.currentThread().getName() + "==" + i);
}
} finally {
// 計數器減1
countDownLatch.countDown();
}
}
}
}
2、Semaphore類
Semaphore是一個控制訪問多個共享資源的計數信號量,用於管理一組資源。它相當於控制使用公共資源的活動線程的數量。在信號量上的兩種操作:
- acquire(獲取) 當一個線程調用acquire操作時,它可以成功獲取信號量(信號量減1),如果沒有就等待。
- release(釋放)釋放信號量(信號量加1),然後喚醒等待的線程。acquire 之後拋異常,信號量不會自動歸還,所以儘量放到 finally 塊中, 防止信號量流失。
例如:一個停車場有5個車位,假設有10臺車進來停車,一個車位同時只能被一臺車使用,只有車使用完開走了,其他車才能繼續使用。
public static void main(String[] args) {
// 5個車位
Semaphore semaphore = new Semaphore(5);
for (int i = 1; i <= 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "搶到車位了");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "離開了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}).start();
}
}
3、CyclicBarrier類
CyclicBarrier 的字面意思是迴環柵欄/可循環使用(Cyclic)的屏障(Barrier),通過它可以實現讓一組線程等待至某個狀態之後再全部同時執行。
它允許一組線程到達某個公共屏障點 (common barrier point)時被阻塞,直到最後一個線程到達屏障(同步點)時,屏障纔會開門,所有被屏障攔截的線程都到齊了纔會繼續幹活。因爲該 barrier 在釋放等待線程後可以重用,所以稱它爲循環 的 barrier。
實例:週末5人組織大巴去旅遊,總共有兩個景點,每個景點約定好遊玩時間,一個景點結束後需要集中一起出發到下一個景點。
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable(){
// 當所有線程到達barrier時執行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "最後一個到達,人齊了!");
}
});
BarrierDemo barrierDemo = new BarrierDemo(cyclicBarrier);
for (int i = 0; i < 5; i++) {
new Thread(barrierDemo).start();
}
}
public static class BarrierDemo implements Runnable {
private CyclicBarrier cyclicBarrier = null;
public BarrierDemo(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "到達景點1");
cyclicBarrier.await();// 線程在這裏等待,直到所有線程都到達barrier。
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "到達景點2" );
cyclicBarrier.await();// 線程在這裏等待,直到所有線程都到達barrier。
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
}
五、JUC- Exchanger(線程之間數據交換)
Exchanger用於進行線程間的數據交換,它提供一個同步點,在這個同步點,兩個線程可以交換彼此的數據
- 兩個線程通過Exchanger.exchange(obj)方法交換數據,如果一個線程先執行exchange方法,它會一直等待第二個線程也執行exchange方法
- 當兩個線程都達到同步點時,這兩個線程就可以交換數據,將本線程生產出來的數據傳遞給對方(只能在兩個線程之間交換數據)
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(new Runnable() {
@Override
public void run() {
try {
String a = "A數據";
String exchangeData = exchanger.exchange(a); //交換我自己的數據,並且獲取別人的數據
System.out.println("線程a:" + exchangeData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
String b = "b數據";
String exchangeData = exchanger.exchange(b); //交換我自己的數據,並且獲取別人的數據
System.out.println("線程b:" + exchangeData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
參考文章: JUC回顧之-AQS同步器的實現原理: