Java 中的Synchronizer

1. 閉鎖(Latch)

    閉鎖是一種Synchronizer,它可以延遲線程的進度直到線程達到終止狀態。一個閉鎖工作起來就像一道大門:直到閉鎖達到終點狀態之前,門一直是關閉的,沒有線程能通過,在終點狀態到來的時候,門開了,允許所有線程都通過。一旦閉鎖到達了終點狀態,它就不能夠再改變狀態了,所以它永遠保持敞開狀態。閉鎖可以用來確保特定活動,直到其他的活動完成之後才發生。

    CountDownLatch是一個靈活的閉鎖實現。允許一個或多個線程等待一個事件集的發生。閉鎖的狀態包括一個計數器,初始化爲一個正數,用來表現需要等待的事件數。countdown方法對計數器做減操作,表示一個事件已經發生了,而await方法等待計數器達到零,此時所有需要等待的事件都已經發生。如果計數器入口時值爲非零,await會一直阻塞到計數器爲零,或者等待線程中斷以超時。

    下面的代碼闡釋了閉鎖的兩種實現方法。

    

import java.util.concurrent.CountDownLatch;

public class TestHarness {
	public long timeTasks(int nThreads, final Runnable task)throws InterruptedException{
		final CountDownLatch startGate = new CountDownLatch(1);
		final CountDownLatch endGate = new CountDownLatch(nThreads);

		for (int i = 0;i < nThreads; ++i){
			Thread t = new Thread(){
				public void run(){
					try{
						startGate.await();
						try{
							task.run();
						}finally{
							endGate.countDown();
						}
					} catch(InterruptedException e){
						e.printStackTrace();
					}
				}
			};
			t.start();
		}
		
		long start = System.nanoTime();
		startGate.countDown();
		endGate.await();
		long end = System.nanoTime();
		return end - start;
	}
	
	
	public static void main(String[] args){
		TestHarness th = new TestHarness();
		Runnable task = new Runnable(){
			public void run(){
				System.out.println("1");
			}
		};
		try {
			th.timeTasks(10, task);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("2");
	}
}
  解釋:TestHarness 創建了一些線程,併發地執行給定的任務。它使用兩個閉鎖,一個“開始閥門”和一個“結束閥門”。這個開始閥門將計數器初始化爲1.結束閥門將計數器初始化爲工作線程的數量。每一個工作線程要做的第一件事就是等待開始閥門打開;這樣做能確保直到所有線程都做好準備時,纔開始工作。每個線程的最後一個工作是爲結束閥門減一;這樣做使控制線程有效地等待,直到最後一個工作線程完成任務,這樣就能計算整個的用時了。

2. FutureTask

    FutureTask同樣可以作爲閉鎖。FutureTask的計算是通過Callable實現的,它等同於一個可攜帶結果的Runnable,並且有3個狀態:等待、運行和完成。完成包括所有計算以任意的方式結束,包括正常結束、取消和異常。一旦FutureTask進入完成狀態,它會永遠停止在這個狀態上。

    Future.get的行爲依賴於任務的狀態。如果它已經完成,get可以立即得到返回結果,否則會被阻塞直到任務轉爲完成狀態,然後會返回結果或者拋出異常。FutureTask把計算的結果從運行計算的線程傳送到需要這個結果的線程:FutureTask的規約保證了這種傳遞建立在結果的安全發佈基礎之上。

    Executor框架利用FutureTask來完成異步任務,並可以用來進行任何潛在的耗時計算,而且可以在真正需要計算結果之前就啓動它們開始計算。如下代碼,Preloader使用了FutureTask來執行一個代價昂貴的計算,結果稍後會被用到;儘早開始計算,你可以減少等待結果所需花費的時間。


package ip2;

import java.util.concurrent.FutureTask;
import java.util.concurrent.Future;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Preloader {
	public final FutureTask<String> future = new FutureTask<String>(new Callable<String>(){
		public String call()  throws Exception {
			return loadString();
		}
	});
	
	
	private String loadString() throws InterruptedException{
		Thread.sleep(1000);
		System.out.println("String");
		return "String";
	}
	
	private final Thread thread = new Thread(future);
	
	public void start(){thread.start();}
	
	public String get() throws InterruptedException, ExecutionException{
		return future.get();
	}
	
	public static void main(String[] args) throws InterruptedException, ExecutionException{
		Preloader p = new Preloader();
		ExecutorService e = Executors.newCachedThreadPool();
		
		Future<String> result = e.submit(new Callable<String>(){
			public String call()  throws Exception {
				return p.loadString();
			}
		});
		Thread.sleep(150);
		System.out.println(result.cancel(false));
		e.shutdown();
		
	}
}

3. 信號量(Semaphore)

    計數信號量用來控制能夠同時訪問某特定資源的活動的數量,或者同時執行某一個給定操作的數量。計數信號量可以用來實現資源池或者給一個容器限定邊界,如下代碼。

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;

public class BoundedHashSet<T> {
	private final Set<T> set;
	private final Semaphore sem;
	public BoundedHashSet(int bound){
		this.set = Collections.synchronizedSet(new HashSet<T>());
		sem = new Semaphore(bound);
	}
	
	public boolean add( T o)throws InterruptedException {
		sem.acquire();
		boolean wasAdded = false;
		try {
			wasAdded = set.add(o);
			return wasAdded;
		}
		
		finally {
			if (!wasAdded){
				sem.release();
			}
		}
	}
	
	public boolean remove(Object o){
		boolean wasRemoved = set.remove(o);
		if (wasRemoved){
			sem.release();
		}
		
		return wasRemoved;
	}
}


4. 關卡(CycleBarrier)

     關卡類似於閉鎖,它們都能夠阻塞一組線程,直到某些事情發生。其中關卡與閉鎖關鍵的不同在於,所有線程必須同時達到關卡點,才能繼續處理。閉鎖等待的是事件;關卡等待的是其他線程。關卡實現的協議,就像一些家庭成員指定商場中的集合地點:“我們每個人6:00在麥當勞見,到了以後不見不撒,之後我們再決定接下來做什麼。”
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CelluarAutomata {
	private final Board mainBoard;
	private final CyclicBarrier barrier;
	private final Worker[] workers;
	public CelluarAutomata(Board board){
		this.mainBoard = board;
		int count = Runtime.getRuntime().availableProcessors();
		this.barrier = new CyclicBarrier(count, new Runnable(){
			public void run(){
				mainBoard.commitNewValues();
			}
		});
		this.workers = new Worker[count];
		
		for (int i = 0;i < count ;i++){
			workers[i] = new Worker(mainBoard.getSubBoard(count ,i));
		}
		
	}
	
	private class Worker implements Runnable{
		private final Board board;
		public Worker(Board board){
			this.board = board;
		}
		
		public void run(){
			while (!board.hasConverged()){
				for (int x = 0; x < board.getMaxX(); x++)
					for (int y = 0; y < board.getMaxY();y++)
						board.setNewValue(x, y, computeValue(x, y));
				try {
					barrier.await();
				} catch (InterruptedException ex){
					return;
				} catch (BrokenBarrierException ex){
					return;
				}
			}
		}

		private Object computeValue(int x, int y) {
			// TODO Auto-generated method stub
			return null;
		}
	}
		
	public void start(){
		for(int i = 0; i < workers.length; i++){
			new Thread(workers[i]).start();
		}
		
		mainBoard.waitForConvergence();
	}
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章