Java併發學習2--Sychronizer
Synchoronizer
接下來說一下同步輔助類的相關內容
1、閉鎖(Latch)
閉鎖的作用是:延遲執行緒的進度直到執行緒達到終點。
原理:閉鎖相當於一個大門,在大門開啟之前,也就是終點狀態到來之前,沒有一個執行緒能夠通過,都處於阻塞狀態。一旦終點狀態到來,大門開啟,則允許所有執行緒通過。一旦閉鎖達到終點狀態,它就不能改變狀態了。
閉鎖可以用來確保特定活動直到其他活動完成之後才發生。
CountDownLatch
一個閉鎖的實現類,初始化為一個正數。閉鎖的狀態是一個計數器,當計數器=0時,則達到終點狀態。
兩個主要方法,
countDown():計數器-1
await():當計算器!=0時,則該執行緒一直阻塞到計算器=0。或者等待到執行緒終點或者超時。
看程式碼:
package sychrionizer;
import java.util.concurrent.CountDownLatch;
public class LathchTest {
public long timeTasks(int sThreads , final Runnable task) throws InterruptedException{
//開始閥門,初始化計數器為1
final CountDownLatch startGate = new CountDownLatch(1);
//結束閥門
final CountDownLatch stopGate = new CountDownLatch(sThreads);
for(int i = 0 ; i < sThreads ; i++){
Thread d = new Thread(){
public void run() {
try {
System.out.println("執行緒"+ Thread.currentThread().getName() + "等待開始標誌...");
startGate.await ();
try {
task.run();
} finally {
System.out.println("執行緒"+ Thread.currentThread().getName() + "結束標誌-1");
stopGate.countDown();
}
} catch (Exception
};
d.start();
}
//計時
long start = System.nanoTime();
System.out.println("開始標誌位-1,開發閥門開啟.....");
startGate.countDown();
System.out.println("結束標誌阻塞,等待結束閥門...");
stopGate.await();
long stop = System.nanoTime();
return stop - start;
}
public static void main(String[] args) {
final Runnable task = new Runnable() {
public void run() {
System.out.println("任務"+Thread.currentThread().getName() + "正在執行");
}
};
try {
System.out.println(new LathchTest().timeTasks(10, task));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
FutureTask
也可以作為閉鎖,FutureTask是通過Callable實現的,它等價於一個可攜帶結果的Runnable,FutureTask具有三個狀態,等待、執行、完成。完成狀態包括以任務方式結束,比如正常結束、取消、異常。一旦FutureTask進入完成狀態,FutureTask會永遠停止在這個狀態上面。
主要方法:
get(),依賴於任務的狀態,如果任務完成,則get可以直接獲取到結果。否則,一直等待,知道獲取到結果或者丟擲異常。
來看看小例子
package sychrionizer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTaskTest {
private final FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
public String call() throws Exception {
System.out.println("結果執行中....");
return "結果..是[1231231231]";
};
});
private final Thread thread = new Thread(future);
/*
* 對外啟動執行緒的方法
*/
public void start(){
System.out.println("開始計算");
thread.start();
}
/*
* 對外獲取結果的執行緒
*/
public String get(){
try {
System.out.println(Thread.currentThread().getName() + "執行緒準備獲取結果!");
return future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
final FutureTaskTest future = new FutureTaskTest();
// 開關開啟
//開始計算
future.start();
for(int i = 0 ; i < 10 ; i++){
Thread t = new Thread(){
public void run() {
System.out.println(future.get());
}
};
t.start();
}
// 開關開啟
//開始計算
//future.start();
}
}
測試程式碼有兩種情況,一種是,future.start();寫在上面,執行結果是:
開始計算
結果執行中....
Thread-1執行緒準備獲取結果!
結果..是[1231231231]
Thread-2執行緒準備獲取結果!
結果..是[1231231231]
Thread-3執行緒準備獲取結果!
結果..是[1231231231]
Thread-4執行緒準備獲取結果!
結果..是[1231231231]
Thread-5執行緒準備獲取結果!
結果..是[1231231231]
Thread-6執行緒準備獲取結果!
結果..是[1231231231]
Thread-7執行緒準備獲取結果!
結果..是[1231231231]
Thread-8執行緒準備獲取結果!
結果..是[1231231231]
Thread-9執行緒準備獲取結果!
結果..是[1231231231]
Thread-10執行緒準備獲取結果!
結果..是[1231231231]
這種情況就是先計算出結果,再去get,可以直接get到結果
第二種情況是start在下面,也就是說先get,再計算,輸出是這樣的:
Thread-2執行緒準備獲取結果!
Thread-6執行緒準備獲取結果!
Thread-7執行緒準備獲取結果!
Thread-5執行緒準備獲取結果!
Thread-4執行緒準備獲取結果!
Thread-3執行緒準備獲取結果!
Thread-1執行緒準備獲取結果!
開始計算
Thread-10執行緒準備獲取結果!
Thread-8執行緒準備獲取結果!
Thread-9執行緒準備獲取結果!
結果執行中....
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
上面的結果說嗎,get()方法如果獲取不到結果,則一直等待到結果為止。
2、Semaphore(訊號量)
Semaphore用來控制能夠同時訪問某特定資源的活動數量。
Semaphore原理:一個Semaphore管理一個有效的許可集(premit),許可的初始量通過構造方法傳入Semaphore。活動能夠獲得許可(在有剩餘許可的前提下),並且在使用之後釋放改許可。如果沒有許可了,那麼acquire會被阻塞,直到有用的許可為止或者中斷或者操作超時。
主要方法:acquire()請求許可,有空餘,則佔領,否則被阻塞,等待一直有空閒的許可位置
release()釋放許可
舉個簡單的例子,上廁所,共有兩個坑,是個人排隊,一次性只能上兩個人,其餘8人的請求只能等待,一直到有坑位位置。
package sychrionizer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class MySemaphore implements Runnable{
//測試位置
private Semaphore position;
private int id;
public MySemaphore(Semaphore position , int id){
this.id = id;
this.position = position;
}
public void run() {
try {
if(position.availablePermits() > 0){
System.out.println("執行緒"+ this.id + "進入,發現有空位");
}else{
System.out.println("執行緒"+ this.id + "進入,發現沒有空位");
}
//獲取到空位置
position.acquire();
System.out.println("執行緒"+ this.id + "獲得了該位置");
Thread.sleep(1000);
System.out.println("執行緒"+ this.id + "使用完畢,離開了該位置");
position.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
Semaphore position = new Semaphore(2);//一共有兩個坑位
//10
for(int i = 0 ; i < 10 ; i++){
pool.submit(new MySemaphore(position, i));
}
//釋放執行緒池
pool.shutdown();
position.acquireUninterruptibly(2);
System.out.println("使用完畢,清掃");
position.release(2);
}
}
以上例子就說明了訊號量的使用過程。