目錄
前置條件:構造一個異步調用
一、使用wait和notify方法
二、使用條件鎖
三、Future
四、使用CountDownLatch
五、使用CyclicBarrier
總結
在Java並發編程中,經常會因為需要提高響應速度而將請求異步化,即將同步請求轉化為異步處理,這是很自然能想到的一種處理方式。相反,在有些場景下也需要將異步處理轉化為同步的方式。
首先介紹一下同步調用和異步調用的概念:
同步調用:調用方在調用過程中,持續等待返回結果。
異步調用:調用方在調用過程中,不直接等待返回結果,而是執行其他任務,結果返回形式通常為回調函數。
其實,兩者的區別還是很明顯的,這里也不再細說,我們主要來說一下Java如何將異步調用轉為同步。換句話說,就是需要在異步調用過程中,持續阻塞至獲得調用結果。接下來將介紹5種Java並發編程中異步轉同步的方法。
- 使用wait和notify方法
- 使用條件鎖
- Future
- 使用CountDownLatch
- 使用CyclicBarrier
前置條件:構造一個異步調用
首先,寫demo需要先寫基礎設施,這里是需要構造一個異步調用模型。異步調用類:
import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AsyncCall { private Random random = new Random(System.currentTimeMillis()); private ExecutorService tp = Executors.newSingleThreadExecutor(); public void call(AbstractBaseDemo demo) { new Thread(() -> { long res = random.nextInt(10); try { Thread.sleep(res * 1000); } catch (InterruptedException e) { e.printStackTrace(); } demo.callback(res); }).start(); } public Future<Long> futureCall() { return tp.submit(() -> { long res = random.nextInt(10); Thread.sleep(res * 1000); return res; }); } public void shutdown() { tp.shutdown(); } }
public abstract class AbstractBaseDemo { protected AsyncCall asyncCall = new AsyncCall(); public abstract void callback(long response); public void call() { System.out.println(Thread.currentThread().getName() + "發起調用"); asyncCall.call(this); System.out.println(Thread.currentThread().getName() + "調用返回"); } }
AbstractBaseDemo非常簡單,里面包含一個異步調用類的實例,另外有一個call方法用於發起異步調用,當然還有一個抽象方法callback需要每個demo去實現的——主要在回調中進行相應的處理來達到異步調用轉同步的目的。
一、使用wait和notify方法
這個方法其實是利用了鎖機制,直接貼代碼:
public class ObjectWaitLockDemo extends AbstractBaseDemo { private final Object lock = new Object(); @Override public void callback(long response) { System.out.println(Thread.currentThread().getName() + "得到結果"); System.out.println(response); System.out.println(Thread.currentThread().getName() + "調用結束"); synchronized (lock) { lock.notifyAll(); } } public static void main(String[] args) { ObjectWaitLockDemo demo = new ObjectWaitLockDemo(); demo.call(); synchronized (demo.lock) { try { demo.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "主線程內容"); } }
沒有使用同步操作的情況下,打印結果:
main發起調用 main調用返回 main主線程內容 Thread-0得到結果 7 Thread-0調用結束
而使用了同步操作后:
main發起調用 main調用返回 Thread-0得到結果 3 Thread-0調用結束 main主線程內容
二、使用條件鎖
和方法一的原理類似:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockDemo extends AbstractBaseDemo { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); @Override public void callback(long response) { System.out.println(Thread.currentThread().getName() + "得到結果"); System.out.println(response); System.out.println(Thread.currentThread().getName() + "調用結束"); lock.lock(); try { condition.signal(); } finally { lock.unlock(); } } public static void main(String[] args) { ReentrantLockDemo demo = new ReentrantLockDemo(); demo.call(); demo.lock.lock(); try { demo.condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { demo.lock.unlock(); } System.out.println(Thread.currentThread().getName() + "主線程內容"); } }
基本上和方法一沒什么區別,只是這里使用了條件鎖,兩者的鎖機制有所不同。
三、Future
使用Future的方法和之前不太一樣,我們調用的異步方法也不一樣。
import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; public class FutureDemo { private AsyncCall asyncCall = new AsyncCall(); public Future<Long> call() { Future<Long> future = asyncCall.futureCall(); asyncCall.shutdown(); return future; } public static void main(String[] args) { FutureDemo demo = new FutureDemo(); System.out.println(Thread.currentThread().getName() + "發起調用"); Future<Long> future = demo.call(); System.out.println(Thread.currentThread().getName() + "返回結果"); while (!future.isDone() && !future.isCancelled()); try { System.out.println(future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "主線程內容"); } }
public void shutdown() { tp.shutdown(); }
四、使用CountDownLatch
使用CountDownLatch或許是日常編程中最常見的一種了,也感覺是相對優雅的一種:
import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo extends AbstractBaseDemo { private final CountDownLatch countDownLatch = new CountDownLatch(1); @Override public void callback(long response) { System.out.println(Thread.currentThread().getName() + "得到結果"); System.out.println(response); System.out.println(Thread.currentThread().getName() + "調用結束"); countDownLatch.countDown(); } public static void main(String[] args) { CountDownLatchDemo demo = new CountDownLatchDemo(); demo.call(); try { demo.countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "主線程內容"); } }
當然,這里和ObjectWaitLockDemo和ReentrantLockDemo中都一樣,主線程中阻塞的部分,都可以設置一個超時時間,超時后可以不再阻塞。
五、使用CyclicBarrier
CyclicBarrier的情況和CountDownLatch有些類似:
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo extends AbstractBaseDemo { private CyclicBarrier cyclicBarrier = new CyclicBarrier(2); @Override public void callback(long response) { System.out.println(Thread.currentThread().getName() + "得到結果"); System.out.println(response); System.out.println(Thread.currentThread().getName() + "調用結束"); try { cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } public static void main(String[] args) { CyclicBarrierDemo demo = new CyclicBarrierDemo(); demo.call(); try { demo.cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "主線程內容"); } }
總結
綜上,就是本次需要說的幾種方法了。事實上,所有的方法都是同一個原理,也就是在調用的線程中進行阻塞等待結果,而在回調中函數中進行阻塞狀態的解除。