死锁
一个任务之间相互等待的连续循环,没有那个线程能继续,称之为死锁。
哲学家就餐:
public class Chopstick {
private boolean taken = false;
public synchronized void take() throws InterruptedException {
while (taken)
wait();
taken = true;
}
public synchronized void drop() {
taken = false;
notifyAll();
}
}
public class Philosopher implements Runnable {
private Chopstick left;
private Chopstick right;
private final int id;
private final int ponderFactor;
private Random rand = new Random(47);
private void pause() throws InterruptedException {
if (ponderFactor == 0) return;
TimeUnit.MILLISECONDS.sleep(
rand.nextInt(ponderFactor * 250));
}
public Philosopher(Chopstick left, Chopstick right,
int ident, int ponder) {
this.left = left;
this.right = right;
id = ident;
ponderFactor = ponder;
}
public void run() {
try {
while (!Thread.interrupted()) {
print(this + " " + "thinking");
pause();
// Philosopher becomes hungry
print(this + " " + "grabbing right");
right.take();
print(this + " " + "grabbing left");
left.take();
print(this + " " + "eating");
pause();
right.drop();
left.drop();
}
} catch (InterruptedException e) {
print(this + " " + "exiting via interrupt");
}
}
public String toString() {
return "Philosopher " + id;
}
}
在Philosopher.run()中,每个Philosopher只是不断思考和吃饭。如果PonderFactor不为0,则pause()方法会休眠一段随机时间。通过使用这种方式,将看到Philosopher会在思考上花掉一段随机的时间,然后尝试着获取左边和右边的Chopstick,随后在吃饭上在花掉随机时间,之后重复此过程。
会产生死锁的版本:
// {Args: 0 5 timeout}
public class DeadlockingDiningPhilosophers {
public static void main(String[] args) throws Exception {
int ponder = 5;
if (args.length > 0)
ponder = Integer.parseInt(args[0]);
int size = 5;
if (args.length > 1)
size = Integer.parseInt(args[1]);
ExecutorService exec = Executors.newCachedThreadPool();
Chopstick[] sticks = new Chopstick[size];
for (int i = 0; i < size; i++)
sticks[i] = new Chopstick();
for (int i = 0; i < size; i++)
exec.execute(new Philosopher(
sticks[i], sticks[(i + 1) % size], i, ponder));
if (args.length == 3 && args[2].equals("timeout"))
TimeUnit.SECONDS.sleep(5);
else {
System.out.println("Press 'Enter' to quit");
System.in.read();
}
exec.shutdownNow();
}
}
如果Philosopher思考的时间非常少,那么死锁会更快的发生,ponder因子可以影响Philosopher思考的时间长度。如果有许多Philosopher或者思考时间很长,那么尽管存在思索的可能,但是可能永远看不到死锁。值为0的命令行参数倾向于死锁尽快发生。
当满足以下四个条件时,就会发生死锁:
1)互斥条件。任务使用的资源中至少有一个是不能共享的。这里,一根Chopstick-次就只能被一个Philosopher使用。
2)至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopber必须拿着一根Chopstick并且等待另一根。
3)资源不能被任务抢占,任务必须把资源释放当作普通事件。Philosopher很有礼貌, 他们不会从其他Philosopher那里抢Chopstick。
4)必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在DeadlockingDiningPhilosophersjava中, 因为每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发生了循环等待。
防止死锁最容易的方法是破坏第四个条件。关键在于必须先右后左。
// {Args: 5 5 timeout}
public class FixedDiningPhilosophers {
public static void main(String[] args) throws Exception {
int ponder = 5;
if (args.length > 0)
ponder = Integer.parseInt(args[0]);
int size = 5;
if (args.length > 1)
size = Integer.parseInt(args[1]);
ExecutorService exec = Executors.newCachedThreadPool();
Chopstick[] sticks = new Chopstick[size];
for (int i = 0; i < size; i++)
sticks[i] = new Chopstick();
for (int i = 0; i < size; i++)
if (i < (size - 1))
exec.execute(new Philosopher(
sticks[i], sticks[i + 1], i, ponder));
else
exec.execute(new Philosopher(
sticks[0], sticks[i], i, ponder));
if (args.length == 3 && args[2].equals("timeout"))
TimeUnit.SECONDS.sleep(5);
else {
System.out.println("Press 'Enter' to quit");
System.in.read();
}
exec.shutdownNow();
}
}
通过确保最后一个Philosopher先拿起和放下左边的Chopstick(最后一个Philosopher将永远不会阻止其右边的Philosopher拿起他们的Chopstick),可以移除死锁。
新类库中的构件
CountDownLatch
它被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成。
CountDownLatch的典型用法是将一个程序分为n个互相独立的可解决任务,并创建值为0的CountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown()。等待问题被解决的任务在这个锁存器上调用await(),将它们自己拦住,直至锁存器计数结束:
class TaskPortion implements Runnable {
private static int counter = 0;
private final int id = counter++;
private static Random rand = new Random(47);
private final CountDownLatch latch;
TaskPortion(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
doWork();
latch.countDown();
} catch (InterruptedException ex) {
// Acceptable way to exit
}
}
public void doWork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(rand.nextInt(2000));
print(this + "completed");
}
public String toString() {
return String.format("%1$-3d ", id);
}
}
// Waits on the CountDownLatch:
class WaitingTask implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final CountDownLatch latch;
WaitingTask(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
latch.await();
print("Latch barrier passed for " + this);
} catch (InterruptedException ex) {
print(this + " interrupted");
}
}
public String toString() {
return String.format("WaitingTask %1$-3d ", id);
}
}
public class CountDownLatchDemo {
static final int SIZE = 100;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
// All must share a single CountDownLatch object:
CountDownLatch latch = new CountDownLatch(SIZE);
for (int i = 0; i < 10; i++)
exec.execute(new WaitingTask(latch));
for (int i = 0; i < SIZE; i++)
exec.execute(new TaskPortion(latch));
print("Launched all tasks");
exec.shutdown(); // Quit when all tasks complete
}
}
TaskPortion将随机地休眠一段时间,以模拟这部分工作的完成,而WaitingTask表示系统中必须等待的部分,它要等待到问题的初始部分完成为止。所有任务都使用了在main()中定义的同一个单一的CountDownLatch。
类库的线程安全
静态的Random对象,意味着多个任何可能会同时调用Ranwom.nextInt()。遗憾的是JDK文档并没有指明那些是线程安全的。Random.nextInt()碰巧是安全的。必须通过使用Web引擎,或者审视Java类库代码,去逐个地揭示这一点。
CyclicBarrier
一组任务并行执行,在进行下一个步骤前等待,直至所有任务都完成所有的并行任务在栅栏处列队,一致地向前移动。
一个用BASIC编写的命名为HOSRAC.BAS的赛马游戏:
class Horse implements Runnable {
private static int counter = 0;
private final int id = counter++;
private int strides = 0;
private static Random rand = new Random(47);
private static CyclicBarrier barrier;
public Horse(CyclicBarrier b) {
barrier = b;
}
public synchronized int getStrides() {
return strides;
}
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
strides += rand.nextInt(3); // Produces 0, 1 or 2
}
barrier.await();
}
} catch (InterruptedException e) {
// A legitimate way to exit
} catch (BrokenBarrierException e) {
// This one we want to know about
throw new RuntimeException(e);
}
}
public String toString() {
return "Horse " + id + " ";
}
public String tracks() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < getStrides(); i++)
s.append("*");
s.append(id);
return s.toString();
}
}
public class HorseRace {
static final int FINISH_LINE = 75;
private List<Horse> horses = new ArrayList<Horse>();
private ExecutorService exec =
Executors.newCachedThreadPool();
private CyclicBarrier barrier;
public HorseRace(int nHorses, final int pause) {
barrier = new CyclicBarrier(nHorses, new Runnable() {
public void run() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < FINISH_LINE; i++)
s.append("="); // The fence on the racetrack
print(s);
for (Horse horse : horses)
print(horse.tracks());
for (Horse horse : horses)
if (horse.getStrides() >= FINISH_LINE) {
print(horse + "won!");
exec.shutdownNow();
return;
}
try {
TimeUnit.MILLISECONDS.sleep(pause);
} catch (InterruptedException e) {
print("barrier-action sleep interrupted");
}
}
});
for (int i = 0; i < nHorses; i++) {
Horse horse = new Horse(barrier);
horses.add(horse);
exec.execute(horse);
}
}
public static void main(String[] args) {
int nHorses = 7;
int pause = 200;
if (args.length > 0) { // Optional argument
int n = new Integer(args[0]);
nHorses = n > 0 ? n : nHorses;
}
if (args.length > 1) { // Optional argument
int p = new Integer(args[1]);
pause = p > -1 ? p : pause;
}
new HorseRace(nHorses, pause);
}
}
可以向CyclicBarrier提供一个栅栏动作,他是一个Runnable,当计数值到达0时自动执行——这是CyclicBarrier和CountDownLatch之间的另一个区别。这里,栅栏动作是做为匿名内部类创建的,它被提交给了CyclicBarrier的构造器。
必须在栅栏处等待其他所有马都准备完毕,当所有的马都向前移动时,CyclicBarrier将自动调用Runnable栅栏动作任务,按顺序显示马和终点线的位置。
DelayQueue
无界的BlockingQueue,放置实现了Delayed的接口对象,对象只能在到期时才能从队列取走。这种队列是有序的,即队头对象的延长到期时间最长。如果没有任何延迟到期,那么就不会有任何头元素,并且poll()将返回null(正因为这样,不能将null放置到这种队列中)。
class DelayedTask implements Runnable, Delayed {
private static int counter = 0;
private final int id = counter++;
private final int delta;
private final long trigger;
protected static List<DelayedTask> sequence =
new ArrayList<DelayedTask>();
public DelayedTask(int delayInMilliseconds) {
delta = delayInMilliseconds;
trigger = System.nanoTime() +
NANOSECONDS.convert(delta, MILLISECONDS);
sequence.add(this);
}
public long getDelay(TimeUnit unit) {
return unit.convert(
trigger - System.nanoTime(), NANOSECONDS);
}
public int compareTo(Delayed arg) {
DelayedTask that = (DelayedTask) arg;
if (trigger < that.trigger) return -1;
if (trigger > that.trigger) return 1;
return 0;
}
public void run() {
printnb(this + " ");
}
public String toString() {
return String.format("[%1$-4d]", delta) +
" Task " + id;
}
public String summary() {
return "(" + id + ":" + delta + ")";
}
public static class EndSentinel extends DelayedTask {
private ExecutorService exec;
public EndSentinel(int delay, ExecutorService e) {
super(delay);
exec = e;
}
public void run() {
for (DelayedTask pt : sequence) {
printnb(pt.summary() + " ");
}
print();
print(this + " Calling shutdownNow()");
exec.shutdownNow();
}
}
}
class DelayedTaskConsumer implements Runnable {
private DelayQueue<DelayedTask> q;
public DelayedTaskConsumer(DelayQueue<DelayedTask> q) {
this.q = q;
}
public void run() {
try {
while (!Thread.interrupted())
q.take().run(); // Run task with the current thread
} catch (InterruptedException e) {
// Acceptable way to exit
}
print("Finished DelayedTaskConsumer");
}
}
public class DelayQueueDemo {
public static void main(String[] args) {
Random rand = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
DelayQueue<DelayedTask> queue =
new DelayQueue<DelayedTask>();
// Fill with tasks that have random delays:
for (int i = 0; i < 20; i++)
queue.put(new DelayedTask(rand.nextInt(5000)));
// Set the stopping point
queue.add(new DelayedTask.EndSentinel(5000, exec));
exec.execute(new DelayedTaskConsumer(queue));
}
} /*
[128 ] Task 11 [200 ] Task 7 [429 ] Task 5 [520 ] Task 18 [555 ] Task 1 [961 ] Task 4 [998 ] Task 16 [1207] Task 9 [1693] Task 2 [1809] Task 14 [1861] Task 3 [2278] Task 15 [3288] Task 10 [3551] Task 12 [4258] Task 0 [4258] Task 19 [4522] Task 8 [4589] Task 13 [4861] Task 17 [4868] Task 6 (0:4258) (1:555) (2:1693) (3:1861) (4:961) (5:429) (6:4868) (7:200) (8:4522) (9:1207) (10:3288) (11:128) (12:3551) (13:4589) (14:1809) (15:2278) (16:998) (17:4861) (18:520) (19:4258) (20:5000)
[5000] Task 20 Calling shutdownNow()
Finished DelayedTaskConsumer
*/
sequence保存了任务被创建的顺序,因此可以看到排序是按照实际发生的顺序执行的。
getDelay(),它用来告知延迟到期有多长时间,或者延迟在多长时间之前已经到期。这个方法将强制去使用TimeUnit类,因为这就是参数类型。delta的值是以毫秒为单位存储的,但是Java SE5的方法System.nanoTime()产生的时间则是以纳秒为单位的。可以转换delta的值,方法是声明它的单位以及以什么单位来表示:
MANOSECONDS.convert(delta.MILLISECONDS);
为了排序,delayed接口还继承了Comparable接口,因此必须实现comparaTo(),使其可以产生合理的比较。
PriorityBlockingQueue
优先级队列,它具有可阻塞的读取操作。下面一个实例,其中在优先级队列中的对象是按照优先级顺序从队列中出现的任务,PrioritizedTask被赋予了一个优先级数字,以此来提供这种顺序:
class PrioritizedTask implements
Runnable, Comparable<PrioritizedTask> {
private Random rand = new Random(47);
private static int counter = 0;
private final int id = counter++;
private final int priority;
protected static List<PrioritizedTask> sequence =
new ArrayList<PrioritizedTask>();
public PrioritizedTask(int priority) {
this.priority = priority;
sequence.add(this);
}
public int compareTo(PrioritizedTask arg) {
return priority < arg.priority ? 1 :
(priority > arg.priority ? -1 : 0);
}
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(rand.nextInt(250));
} catch (InterruptedException e) {
// Acceptable way to exit
}
print(this);
}
public String toString() {
return String.format("[%1$-3d]", priority) +
" Task " + id;
}
public String summary() {
return "(" + id + ":" + priority + ")";
}
public static class EndSentinel extends PrioritizedTask {
private ExecutorService exec;
public EndSentinel(ExecutorService e) {
super(-1); // Lowest priority in this program
exec = e;
}
public void run() {
int count = 0;
for (PrioritizedTask pt : sequence) {
printnb(pt.summary());
if (++count % 5 == 0)
print();
}
print();
print(this + " Calling shutdownNow()");
exec.shutdownNow();
}
}
}
class PrioritizedTaskProducer implements Runnable {
private Random rand = new Random(47);
private Queue<Runnable> queue;
private ExecutorService exec;
public PrioritizedTaskProducer(
Queue<Runnable> q, ExecutorService e) {
queue = q;
exec = e; // Used for EndSentinel
}
public void run() {
// Unbounded queue; never blocks.
// Fill it up fast with random priorities:
for (int i = 0; i < 20; i++) {
queue.add(new PrioritizedTask(rand.nextInt(10)));
Thread.yield();
}
// Trickle in highest-priority jobs:
try {
for (int i = 0; i < 10; i++) {
TimeUnit.MILLISECONDS.sleep(250);
queue.add(new PrioritizedTask(10));
}
// Add jobs, lowest priority first:
for (int i = 0; i < 10; i++)
queue.add(new PrioritizedTask(i));
// A sentinel to stop all the tasks:
queue.add(new PrioritizedTask.EndSentinel(exec));
} catch (InterruptedException e) {
// Acceptable way to exit
}
print("Finished PrioritizedTaskProducer");
}
}
class PrioritizedTaskConsumer implements Runnable {
private PriorityBlockingQueue<Runnable> q;
public PrioritizedTaskConsumer(
PriorityBlockingQueue<Runnable> q) {
this.q = q;
}
public void run() {
try {
while (!Thread.interrupted())
// Use current thread to run the task:
q.take().run();
} catch (InterruptedException e) {
// Acceptable way to exit
}
print("Finished PrioritizedTaskConsumer");
}
}
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws Exception {
Random rand = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
PriorityBlockingQueue<Runnable> queue =
new PriorityBlockingQueue<Runnable>();
exec.execute(new PrioritizedTaskProducer(queue, exec));
exec.execute(new PrioritizedTaskConsumer(queue));
}
}
PrioritizedTask对象的创建序列被记录在sequence List中,用于和实际的执行顺序比较。run()方法将休眠一小段随机的时间,然后打印对象信息,而EndSentinel提供了和前面相同的功能,要确保它是队列中最后一个对象。
PrioritizedTaskProducer和PrioritizedTaskConsumer通过PriorityBlockingQueue彼此连接。因为这种队列的阻塞特性提供了所有必需的同步,所以这里不需要任何显式的同步——不必考虑当从这种队列中读取时,其中是否有元素,因为这给队列在没有元素时,将直接阻塞读取者。
使用ScheduledExecutor的温室控制器
可以控制各种设施的开关,或者是对它们进行调节。每个期望的温室时间都是一个在预定时间运行的任务:
public class GreenhouseScheduler {
private volatile boolean light = false;
private volatile boolean water = false;
private String thermostat = "Day";
public synchronized String getThermostat() {
return thermostat;
}
public synchronized void setThermostat(String value) {
thermostat = value;
}
ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(10);
public void schedule(Runnable event, long delay) {
scheduler.schedule(event, delay, TimeUnit.MILLISECONDS);
}
public void
repeat(Runnable event, long initialDelay, long period) {
scheduler.scheduleAtFixedRate(
event, initialDelay, period, TimeUnit.MILLISECONDS);
}
class LightOn implements Runnable {
public void run() {
// Put hardware control code here to
// physically turn on the light.
System.out.println("Turning on lights");
light = true;
}
}
class LightOff implements Runnable {
public void run() {
// Put hardware control code here to
// physically turn off the light.
System.out.println("Turning off lights");
light = false;
}
}
class WaterOn implements Runnable {
public void run() {
// Put hardware control code here.
System.out.println("Turning greenhouse water on");
water = true;
}
}
class WaterOff implements Runnable {
public void run() {
// Put hardware control code here.
System.out.println("Turning greenhouse water off");
water = false;
}
}
class ThermostatNight implements Runnable {
public void run() {
// Put hardware control code here.
System.out.println("Thermostat to night setting");
setThermostat("Night");
}
}
class ThermostatDay implements Runnable {
public void run() {
// Put hardware control code here.
System.out.println("Thermostat to day setting");
setThermostat("Day");
}
}
class Bell implements Runnable {
public void run() {
System.out.println("Bing!");
}
}
class Terminate implements Runnable {
public void run() {
System.out.println("Terminating");
scheduler.shutdownNow();
// Must start a separate task to do this job,
// since the scheduler has been shut down:
new Thread() {
public void run() {
for (DataPoint d : data)
System.out.println(d);
}
}.start();
}
}
// New feature: data collection
static class DataPoint {
final Calendar time;
final float temperature;
final float humidity;
public DataPoint(Calendar d, float temp, float hum) {
time = d;
temperature = temp;
humidity = hum;
}
public String toString() {
return time.getTime() +
String.format(
" temperature: %1$.1f humidity: %2$.2f",
temperature, humidity);
}
}
private Calendar lastTime = Calendar.getInstance();
{ // Adjust date to the half hour
lastTime.set(Calendar.MINUTE, 30);
lastTime.set(Calendar.SECOND, 00);
}
private float lastTemp = 65.0f;
private int tempDirection = +1;
private float lastHumidity = 50.0f;
private int humidityDirection = +1;
private Random rand = new Random(47);
List<DataPoint> data = Collections.synchronizedList(
new ArrayList<DataPoint>());
class CollectData implements Runnable {
public void run() {
System.out.println("Collecting data");
synchronized (GreenhouseScheduler.this) {
// Pretend the interval is longer than it is:
lastTime.set(Calendar.MINUTE,
lastTime.get(Calendar.MINUTE) + 30);
// One in 5 chances of reversing the direction:
if (rand.nextInt(5) == 4)
tempDirection = -tempDirection;
// Store previous value:
lastTemp = lastTemp +
tempDirection * (1.0f + rand.nextFloat());
if (rand.nextInt(5) == 4)
humidityDirection = -humidityDirection;
lastHumidity = lastHumidity +
humidityDirection * rand.nextFloat();
// Calendar must be cloned, otherwise all
// DataPoints hold references to the same lastTime.
// For a basic object like Calendar, clone() is OK.
data.add(new DataPoint((Calendar) lastTime.clone(),
lastTemp, lastHumidity));
}
}
}
public static void main(String[] args) {
GreenhouseScheduler gh = new GreenhouseScheduler();
gh.schedule(gh.new Terminate(), 5000);
// Former "Restart" class not necessary:
gh.repeat(gh.new Bell(), 0, 1000);
gh.repeat(gh.new ThermostatNight(), 0, 2000);
gh.repeat(gh.new LightOn(), 0, 200);
gh.repeat(gh.new LightOff(), 0, 400);
gh.repeat(gh.new WaterOn(), 0, 600);
gh.repeat(gh.new WaterOff(), 0, 800);
gh.repeat(gh.new ThermostatDay(), 0, 1400);
gh.repeat(gh.new CollectData(), 500, 500);
}
}
这个版本重新组织了代码,并且添加了新特性:收集温室内的温度和湿度读数。DataPoint可以持有并显示单个的数据段,而CollectData是被调度的任务,他在每次运行时,都可以产生仿真数据,并将其添加到Greenhouse的List中。
在持有DataPoint的List中的所有方法都是synchronized的,这是因为在List被创建时,使用了java.util.Collections实用工具synchronizedList();
Semaphore
正常的锁在任何时刻都只允许一个任务访问一项资源,而计数信号量允许n个任务同时访问这个资源。还可以将信号量看作是向外分发使用资源的“许可证”。
考虑对象池的概念,它管理着数量有限的对象,当要使用对象时可以签出它们,而在用户使用完毕时,可以将它们签回。这种功能可以被封装到一个泛型类中:
public class Pool<T> {
private int size;
private List<T> items = new ArrayList<T>();
private volatile boolean[] checkedOut;
private Semaphore available;
public Pool(Class<T> classObject, int size) {
this.size = size;
checkedOut = new boolean[size];
available = new Semaphore(size, true);
// Load pool with objects that can be checked out:
for (int i = 0; i < size; ++i)
try {
// Assumes a default constructor:
items.add(classObject.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public T checkOut() throws InterruptedException {
available.acquire();
return getItem();
}
public void checkIn(T x) {
if (releaseItem(x))
available.release();
}
private synchronized T getItem() {
for (int i = 0; i < size; ++i)
if (!checkedOut[i]) {
checkedOut[i] = true;
return items.get(i);
}
return null; // Semaphore prevents reaching here
}
private synchronized boolean releaseItem(T item) {
int index = items.indexOf(item);
if (index == -1) return false; // Not in the list
if (checkedOut[index]) {
checkedOut[index] = false;
return true;
}
return false; // Wasn't checked out
}
}
在这个简化的形式中,构造器使用newInstance()来把对象加载到池中。需要一个新对象,那么可以调用checkOut(),并且在使用完之后,将其递交给checkIn()。
boolean类型的数组checkedOut可以跟踪被签出的对象,并且可以通过getItem()和releaseItem()方法来管理。而这些都将由Semaphore类型的available来加以确保,因此,在checkOut()中,如果没有任何信号量许可证可用,available将阻塞调用过程。在checkIn()中,如果被签入的对象有效,则会想信号量返回一个许可证。
public class Fat {
private volatile double d; // Prevent optimization
private static int counter = 0;
private final int id = counter++;
public Fat() {
// Expensive, interruptible operation:
for (int i = 1; i < 10000; i++) {
d += (Math.PI + Math.E) / (double) i;
}
}
public void operation() {
System.out.println(this);
}
public String toString() {
return "Fat id: " + id;
}
}
在池中管理这些对象,以限制这个构造器所造成的影响。可以创建一个任务,它将签出Fat对象,持有一段时间之后再将它们签入,以此来测试Pool这个类:
class CheckoutTask<T> implements Runnable {
private static int counter = 0;
private final int id = counter++;
private Pool<T> pool;
public CheckoutTask(Pool<T> pool) {
this.pool = pool;
}
public void run() {
try {
T item = pool.checkOut();
print(this + "checked out " + item);
TimeUnit.SECONDS.sleep(1);
print(this + "checking in " + item);
pool.checkIn(item);
} catch (InterruptedException e) {
// Acceptable way to terminate
}
}
public String toString() {
return "CheckoutTask " + id + " ";
}
}
public class SemaphoreDemo {
final static int SIZE = 25;
public static void main(String[] args) throws Exception {
final Pool<Fat> pool =
new Pool<Fat>(Fat.class, SIZE);
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < SIZE; i++)
exec.execute(new CheckoutTask<Fat>(pool));
print("All CheckoutTasks created");
List<Fat> list = new ArrayList<Fat>();
for (int i = 0; i < SIZE; i++) {
Fat f = pool.checkOut();
printnb(i + ": main() thread checked out ");
f.operation();
list.add(f);
}
Future<?> blocked = exec.submit(new Runnable() {
public void run() {
try {
// Semaphore prevents additional checkout,
// so call is blocked:
pool.checkOut();
} catch (InterruptedException e) {
print("checkOut() Interrupted");
}
}
});
TimeUnit.SECONDS.sleep(2);
blocked.cancel(true); // Break out of blocked call
print("Checking in objects in " + list);
for (Fat f : list)
pool.checkIn(f);
for (Fat f : list)
pool.checkIn(f); // Second checkIn ignored
exec.shutdown();
}
}
main()线程签出池中的Fat对象,但是并不签入它们。一旦池中所有的对象都被签出,Semaphore将不再允许执行任何签出操作。blocked的run()方法因此会被阻塞,2秒钟之后,cancel()方法被调用,以此来挣脱Future的束缚。
Exchanger
Exchanger是在两个任务之间交换对象的栅栏。当这些任务进入栅栏时,他们各自拥有一个对象,当它们离开时,它们都拥有之前由对象持有的对象。
class ExchangerProducer<T> implements Runnable {
private Generator<T> generator;
private Exchanger<List<T>> exchanger;
private List<T> holder;
ExchangerProducer(Exchanger<List<T>> exchg,
Generator<T> gen, List<T> holder) {
exchanger = exchg;
generator = gen;
this.holder = holder;
}
public void run() {
try {
while (!Thread.interrupted()) {
for (int i = 0; i < ExchangerDemo.size; i++)
holder.add(generator.next());
// Exchange full for empty:
holder = exchanger.exchange(holder);
}
} catch (InterruptedException e) {
// OK to terminate this way.
}
}
}
class ExchangerConsumer<T> implements Runnable {
private Exchanger<List<T>> exchanger;
private List<T> holder;
private volatile T value;
ExchangerConsumer(Exchanger<List<T>> ex, List<T> holder) {
exchanger = ex;
this.holder = holder;
}
public void run() {
try {
while (!Thread.interrupted()) {
holder = exchanger.exchange(holder);
for (T x : holder) {
value = x; // Fetch out value
holder.remove(x); // OK for CopyOnWriteArrayList
}
}
} catch (InterruptedException e) {
// OK to terminate this way.
}
System.out.println("Final value: " + value);
}
}
public class ExchangerDemo {
static int size = 10;
static int delay = 5; // Seconds
public static void main(String[] args) throws Exception {
if (args.length > 0)
size = new Integer(args[0]);
if (args.length > 1)
delay = new Integer(args[1]);
ExecutorService exec = Executors.newCachedThreadPool();
Exchanger<List<Fat>> xc = new Exchanger<List<Fat>>();
List<Fat>
producerList = new CopyOnWriteArrayList<Fat>(),
consumerList = new CopyOnWriteArrayList<Fat>();
exec.execute(new ExchangerProducer<Fat>(xc,
BasicGenerator.create(Fat.class), producerList));
exec.execute(
new ExchangerConsumer<Fat>(xc, consumerList));
TimeUnit.SECONDS.sleep(delay);
exec.shutdownNow();
}
} /*
Final value: Fat id: 29999
*/
在main()中,创建了用于两个任务的单一的Exchanger,以及两个用于交换的CopyOnWritrArrayList。这个特定的List变体允许在列表被遍历时调用remove()方法,而不会抛出ConcurrentModificationException异常。ExchangeProducer将填充这个List,然后将这个满列表交换为ExchangerConsumer传递给它的空列表。因为有了Exchanger,填充一个列表和消费另一个列表便可以同时发生了。