死锁
当两个任务相互等待时,就会出现死锁现象。当一运行就死锁的话不是问题,你能够跟踪并找到问题。真正的问题在于,程序可能看起来工作良好,但是具有潜在的死锁危险。这时,死锁可能发生,而事先却没有任何征兆,直到客户发现他意外的出现(并且会很难重现)。
所以,在编写并发程序时,进行仔细的程序设计以防止死锁是关键部分。
这里引入哲学家就餐 问题来研究死锁问题。
有5位哲学家,他们花部分时间思考,部分时间就餐;当他们就餐时,就需要用两根筷子。然后他们只有5根筷子(与哲学家人数相同),当一位哲学家就餐,他就必须同时得到左边和右边的筷子。如果这两根筷子已经有人在使用了,那么这个哲学家就必须等待。
筷子类:
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;
}
} ///:~
在哲学家的run方法里,每个philosopher不断的思考与吃饭。
执行过程:
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();
}
}
正如注释,当哲学家思考时间长的时候,很难发生死锁,但当哲学家思考时间很短时,就可能发生死锁,这就是死锁问题最麻烦的地方。
要修正死锁问题,必须明白,当以下四个条件同时满足时,就会发生死锁:
- 互斥条件。任务使用的资源中至少有一个是不能共享的,正如上例中一根筷子一次只能给一个哲学家使用。
- 至少有一个任务必须持有一个资源,且等待获取当前被别的任务持有的资源。
- 资源不能被任务抢占,任务必须把资源释放当做普通条件。正如哲学家不会从其他哲学家那里抢夺筷子。
- 必须有循环等待。
当这些条件同时满足时,就会发生死锁。如果要防止死锁,可以从破坏这4个条件其中一个即可。
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));
新类库中的构建
Java SE5的并发类里引入了大量设计来帮助解决并发问题,在接下来逐个介绍。
CountDownLatch - 计数器
它被用来同步一个或多个任务,强制等待一些任务完了之后执行下一步的任务。
使用方法:初始化一个计数器,参数中的int表示计数值:
static final int SIZE = 100;
...
CountDownLatch latch = new CountDownLatch(SIZE);
预先跑的任务在完成一次的时候调用countDown()方法,会使计数器减一。
public void run() {
try {
doWork();
latch.countDown();
} catch (InterruptedException ex) {
// Acceptable way to exit
}
}
需要等待的任务调用await()方法使自己阻塞,直到计数器归零才会继续运行接下来的代码。
public void run() {
try {
latch.await();//必须等待
print("Latch barrier passed for " + this);
} catch (InterruptedException ex) {
print(this + " interrupted");
}
}
完整例子:https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/CountDownLatchDemo.java
CyclicBarrier - 周期性的障碍
效果有点像join,任务会执行工作,然后遇到栅栏 后等待,再一起继续运行。
等待代码: 与CountDownLatch一样
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);
}
}
构造方式:
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");
}
}
});
这个例子采取了匿名内部类的方式,CyclicBarrier的构造参数是(int,Runable()),一个int类型和一个runable()类型,理解为,所有任务先运行,遇到await()时停止,开始运行栅栏处 (即构造函数中的Runable),等运行完栅栏后,再继续原来的任务。 int表示同步的任务数,每个任务await都会使得CyclcBarrier计数减一,当为0时则运行runable()。
完整代码:https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/HorseRace.java
DelayQueue 延迟队列
放置Delayed接口的对象,只有到期后才能被取走,否则为null。
Delayed接口实现:
//Delay接口实现
public long getDelay(TimeUnit unit) {
return unit.convert(
trigger - System.nanoTime(), NANOSECONDS);
}
//Delay接口实现
public int compareTo(Delayed arg) {
DelayedTask that = (DelayedTask) arg;
if (trigger < that.trigger) return -1;
if (trigger > that.trigger) return 1;
return 0;
}
初始化与装载:
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)));//按延迟顺序放入队列
直接放置在最后:
queue.add(new DelayedTask.EndSentinel(5000, exec));
获取元素:
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");
}
如果没有任何延迟到期,就不会有任何头元素,pull()就会返回null。
完整例子:https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/DelayQueueDemo.java
PriorityBlockingQueue
优先级队列,会以优先级的顺序从队列里取出。
初始化:
PriorityBlockingQueue<Runnable> queue =
new PriorityBlockingQueue<Runnable>();
取出:
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");
}
实现的Comparable<T>接口
public int compareTo(PrioritizedTask arg) {
return priority < arg.priority ? 1 :
(priority > arg.priority ? -1 : 0);
}
ScheduledExecutor 计划任务执行器
初始化:
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);
}
Semaphore 信号量
信号量允许n个任务同时访问资源、可以把信号量看做向外分发资源的“许可证”,但实际上没有使用任何的对象。
构造:
Semaphore available = new Semaphore(size, true);
核心方法:
available.acquire();
Each acquire() blocks if necessary until a permit is available, and then takes it.
available.release();
Each release() adds a permit, potentially releasing a blocking acquirer. However, no actual permit objects are used
the Semaphore just keeps a count of the number available and acts accordingly.
这个信号量经常在对象池 中被使用。
参考API的文档,信号量Semaphore只是保存了可用对象的数量,每当acqueire()则减1,每当release则增加1个许可,当没有对象时,信号量就会产生阻塞。
对象池例子:
https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/Pool.java
测试对象、测试类:
https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/Fat.java
https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/SemaphoreDemo.java
Exchanger 交换对象的栅栏
Exchanger可以实现的效果是:生产者一旦生产对象,消费者立刻消费对象,即被创建的同时被消费。
初始化:
Exchanger<List<Fat>> xc = new Exchanger<List<Fat>>();
核心方法(Exchanger.exchange()):
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.
}
}
完整例子:
https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/ExchangerDemo.java
仿真
并发最有趣的用法就是仿真。通过使用并发,每一个构建都可以成为自身的任务。
银行出纳员仿真
这是个经典而又有趣的例子,他可以表现任何属于如下类型的情况:
对象随机的出现,并且要求数量有限的服务器提供随机的服务时间。通过仿真能确定理想的服务器数量。
每个银行顾客要求一定的服务时间,银行柜员必须花费这些时间在他们身上。服务时间对于每个顾客都是不同的。另外你不知道每个时间间隔会有多少顾客到达。
仿真的实现如下:
客户: 简单的只读对象
// Read-only objects don't require synchronization:
class Customer {
private final int serviceTime;
public Customer(int tm) {
serviceTime = tm;
}
public int getServiceTime() {
return serviceTime;
}
public String toString() {
return "[" + serviceTime + "]";
}
}
客户工作队列: 抽象化客户工作队列,即一个柜员需要工作的客户队列。
// Teach the customer line to display itself:
class CustomerLine extends ArrayBlockingQueue<Customer> {
public CustomerLine(int maxLineSize) {
super(maxLineSize);
}
public String toString() {
if (this.size() == 0)
return "[Empty]";
StringBuilder result = new StringBuilder();
for (Customer customer : this)
result.append(customer);
return result.toString();
}
}
客户生成器 : 拥有一个客户工作队列,随机的向这个工作队列添加客户
// Randomly add customers to a queue:
class CustomerGenerator implements Runnable {
private CustomerLine customers;
private static Random rand = new Random(47);
public CustomerGenerator(CustomerLine cq) {
customers = cq;
}
public void run() {
try {
while (!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(rand.nextInt(300));
customers.put(new Customer(rand.nextInt(1000)));
}
} catch (InterruptedException e) {
System.out.println("CustomerGenerator interrupted");
}
System.out.println("CustomerGenerator terminating");
}
}
柜员: 每个柜员有一个客户工作队列,工作时从队列中取出客户,并花费客户需要的时间,并有一个int域保存服务过的客户的数量,以此为依据实现Comparable接口。
实现2个状态,doSomethingElse()和serveCustomLine(),可以改变柜员的工作状态
class Teller implements Runnable, Comparable<Teller> {
private static int counter = 0;
private final int id = counter++;
// Customers served during this shift:
private int customersServed = 0;
private CustomerLine customers;
private boolean servingCustomerLine = true;
public Teller(CustomerLine cq) {
customers = cq;
}
public void run() {
try {
while (!Thread.interrupted()) {
Customer customer = customers.take();
TimeUnit.MILLISECONDS.sleep(
customer.getServiceTime());
synchronized (this) {
customersServed++;
while (!servingCustomerLine)
wait();
}
}
} catch (InterruptedException e) {
System.out.println(this + "interrupted");
}
System.out.println(this + "terminating");
}
public synchronized void doSomethingElse() {
customersServed = 0;
servingCustomerLine = false;
}
public synchronized void serveCustomerLine() {
assert !servingCustomerLine : "already serving: " + this;
servingCustomerLine = true;
notifyAll();
}
public String toString() {
return "Teller " + id + " ";
}
public String shortString() {
return "T" + id;
}
// Used by priority queue:
public synchronized int compareTo(Teller other) {
return customersServed < other.customersServed ? -1 :
(customersServed == other.customersServed ? 0 : 1);
}
}
柜员管理: 命名有点不妥,不只是柜员的管理,是全部活动的中心。
该类中最有趣的方法是adjustTellerNumber(),他尝试着给客户分配数量合理的柜员。
class TellerManager implements Runnable {
private ExecutorService exec;
private CustomerLine customers;
private PriorityQueue<Teller> workingTellers =
new PriorityQueue<Teller>();
private Queue<Teller> tellersDoingOtherThings =
new LinkedList<Teller>();
private int adjustmentPeriod;//调整的频率
private static Random rand = new Random(47);
public TellerManager(ExecutorService e,
CustomerLine customers, int adjustmentPeriod) {
exec = e;
this.customers = customers;
this.adjustmentPeriod = adjustmentPeriod;
// Start with a single teller:
Teller teller = new Teller(customers);
exec.execute(teller);
workingTellers.add(teller);
}
public void adjustTellerNumber() {
// This is actually a control system. By adjusting
// the numbers, you can reveal stability issues in
// the control mechanism.
// If line is too long, add another teller:
if (customers.size() / workingTellers.size() > 2) {
// If tellers are on break or doing
// another job, bring one back:
if (tellersDoingOtherThings.size() > 0) {
Teller teller = tellersDoingOtherThings.remove();
teller.serveCustomerLine();
workingTellers.offer(teller);
return;
}
// Else create (hire) a new teller
Teller teller = new Teller(customers);
exec.execute(teller);
workingTellers.add(teller);
return;
}
// If line is short enough, remove a teller:
if (workingTellers.size() > 1 &&
customers.size() / workingTellers.size() < 2)
reassignOneTeller();
// If there is no line, we only need one teller:
if (customers.size() == 0)
while (workingTellers.size() > 1)
reassignOneTeller();
}
// Give a teller a different job or a break:
private void reassignOneTeller() {
Teller teller = workingTellers.poll();
teller.doSomethingElse();
tellersDoingOtherThings.offer(teller);
}
public void run() {
try {
while (!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(adjustmentPeriod);
adjustTellerNumber();
System.out.print(customers + " { ");
for (Teller teller : workingTellers)
System.out.print(teller.shortString() + " ");
System.out.println("}");
}
} catch (InterruptedException e) {
System.out.println(this + "interrupted");
}
System.out.println(this + "terminating");
}
public String toString() {
return "TellerManager ";
}
}
执行:
public class BankTellerSimulation {
static final int MAX_LINE_SIZE = 50;
static final int ADJUSTMENT_PERIOD = 1000;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
// If line is too long, customers will leave:
CustomerLine customers =
new CustomerLine(MAX_LINE_SIZE);
exec.execute(new CustomerGenerator(customers));
// Manager will add and remove tellers as necessary:
exec.execute(new TellerManager(
exec, customers, ADJUSTMENT_PERIOD));
if (args.length > 0) // Optional argument
TimeUnit.SECONDS.sleep(new Integer(args[0]));
else {
System.out.println("Press 'Enter' to quit");
System.in.read();
}
exec.shutdownNow();
}
饭店仿真
食物:
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
enum Potato implements Food{
BLACK,WHITE;
}
} ///:~
菜单:
// This is given to the waiter, who gives it to the chef:
class Order { // (A data-transfer object)
private static int counter = 0;
private final int id = counter++;
private final Customer customer;
private final WaitPerson waitPerson;
private final Food food;
public Order(Customer cust, WaitPerson wp, Food f) {
customer = cust;
waitPerson = wp;
food = f;
}
public Food item() {
return food;
}
public Customer getCustomer() {
return customer;
}
public WaitPerson getWaitPerson() {
return waitPerson;
}
public String toString() {
return "Order: " + id + " item: " + food +
" for: " + customer +
" served by: " + waitPerson;
}
}
盘子:
// This is what comes back from the chef:
class Plate {
private final Order order;
private final Food food;
public Plate(Order ord, Food f) {
order = ord;
food = f;
}
public Order getOrder() {
return order;
}
public Food getFood() {
return food;
}
public String toString() {
return food.toString();
}
}
客户: 使用了SynchronousQueue,该队列是一个没有内容的阻塞队列,每一个put()都必须等待一个take()。
class Customer implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final WaitPerson waitPerson;
// Only one course at a time can be received:
private SynchronousQueue<Plate> placeSetting =
new SynchronousQueue<Plate>();
public Customer(WaitPerson w) {
waitPerson = w;
}
public void
deliver(Plate p) throws InterruptedException {
// Only blocks if customer is still
// eating the previous course:
placeSetting.put(p);
}
public void run() {
for (Course course : Course.values()) {
Food food = course.randomSelection();
try {
waitPerson.placeOrder(this, food);
// Blocks until course has been delivered:
print(this + "eating " + placeSetting.take());
} catch (InterruptedException e) {
print(this + "waiting for " +
course + " interrupted");
break;
}
}
print(this + "finished meal, leaving");
}
public String toString() {
return "Customer " + id + " ";
}
}
服务员:
class WaitPerson implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final Restaurant restaurant;
BlockingQueue<Plate> filledOrders =
new LinkedBlockingQueue<Plate>();
public WaitPerson(Restaurant rest) {
restaurant = rest;
}
public void placeOrder(Customer cust, Food food) {
try {
// Shouldn't actually block because this is
// a LinkedBlockingQueue with no size limit:
restaurant.orders.put(new Order(cust, this, food));
} catch (InterruptedException e) {
print(this + " placeOrder interrupted");
}
}
public void run() {
try {
while (!Thread.interrupted()) {
// Blocks until a course is ready
Plate plate = filledOrders.take();
print(this + "received " + plate +
" delivering to " +
plate.getOrder().getCustomer());
plate.getOrder().getCustomer().deliver(plate);
}
} catch (InterruptedException e) {
print(this + " interrupted");
}
print(this + " off duty");
}
public String toString() {
return "WaitPerson " + id + " ";
}
}
大厨:
class Chef implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final Restaurant restaurant;
private static Random rand = new Random(47);
public Chef(Restaurant rest) {
restaurant = rest;
}
public void run() {
try {
while (!Thread.interrupted()) {
// Blocks until an order appears:
Order order = restaurant.orders.take();
Food requestedItem = order.item();
// Time to prepare order:
TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
Plate plate = new Plate(order, requestedItem);
order.getWaitPerson().filledOrders.put(plate);
}
} catch (InterruptedException e) {
print(this + " interrupted");
}
print(this + " off duty");
}
public String toString() {
return "Chef " + id + " ";
}
}
餐厅:
class Restaurant implements Runnable {
private List<WaitPerson> waitPersons =
new ArrayList<WaitPerson>();
private List<Chef> chefs = new ArrayList<Chef>();
private ExecutorService exec;
private static Random rand = new Random(47);
BlockingQueue<Order>
orders = new LinkedBlockingQueue<Order>();
public Restaurant(ExecutorService e, int nWaitPersons,
int nChefs) {
exec = e;
for (int i = 0; i < nWaitPersons; i++) {
WaitPerson waitPerson = new WaitPerson(this);
waitPersons.add(waitPerson);
exec.execute(waitPerson);
}
for (int i = 0; i < nChefs; i++) {
Chef chef = new Chef(this);
chefs.add(chef);
exec.execute(chef);
}
}
public void run() {
try {
while (!Thread.interrupted()) {
// A new customer arrives; assign a WaitPerson:
WaitPerson wp = waitPersons.get(
rand.nextInt(waitPersons.size()));
Customer c = new Customer(wp);
exec.execute(c);
TimeUnit.MILLISECONDS.sleep(100);
}
} catch (InterruptedException e) {
print("Restaurant interrupted");
}
print("Restaurant closing");
}
}
主程序:
public class RestaurantWithQueues {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
Restaurant restaurant = new Restaurant(exec, 5, 2);
exec.execute(restaurant);
if (args.length > 0) // Optional argument
TimeUnit.SECONDS.sleep(new Integer(args[0]));
else {
print("Press 'Enter' to quit");
System.in.read();
}
exec.shutdownNow();
}
}
这个例子所有元素都很直观,需要注意的是在元素之间传递消息的队列:
Restaurant中的orders队列记录了所有customer下的订单。
WaitPerson中的filledOrders队列记录了所有已完成的订单。其中大厨从orders队列中获取订单,完成后将盘子放入filledOrders队列。
而Customer有一个上盘子队列placeSetting,只有当调用deliver()方法后,客户才能吃到食物。
这个仿真的例子通过这3个工作队列极大的简化了并发编程的过程,是个优秀的例子。
完整例子:https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/restaurant2/RestaurantWithQueues.java
分发工作
注意下面这个仿真例子,考虑一个假想的用于汽车的机器人组转线,每辆Car都将分为多个阶段构建,从创建地盘开始,紧跟着是安装发动机、车厢和轮子。
汽车:
class Car {
private final int id;
private boolean
engine = false, driveTrain = false, wheels = false;
public Car(int idn) {
id = idn;
}
// Empty Car object:
public Car() {
id = -1;
}
public synchronized int getId() {
return id;
}
public synchronized void addEngine() {
engine = true;
}
public synchronized void addDriveTrain() {
driveTrain = true;
}
public synchronized void addWheels() {
wheels = true;
}
public synchronized String toString() {
return "Car " + id + " [" + " engine: " + engine
+ " driveTrain: " + driveTrain
+ " wheels: " + wheels + " ]";
}
}
汽车队列:
class CarQueue extends LinkedBlockingQueue<Car> {
}
底盘构造器: 命名有点歧义,直接生产汽车至汽车队列
class ChassisBuilder implements Runnable {
private CarQueue carQueue;
private int counter = 0;
public ChassisBuilder(CarQueue cq) {
carQueue = cq;
}
public void run() {
try {
while (!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(500);
// Make chassis:
Car c = new Car(counter++);
print("ChassisBuilder created " + c);
// Insert into queue
carQueue.put(c);
}
} catch (InterruptedException e) {
print("Interrupted: ChassisBuilder");
}
print("ChassisBuilder off");
}
}
装配任务:
class Assembler implements Runnable {
private CarQueue chassisQueue, finishingQueue;
private Car car;
private CyclicBarrier barrier = new CyclicBarrier(4);
private RobotPool robotPool;
public Assembler(CarQueue cq, CarQueue fq, RobotPool rp) {
chassisQueue = cq;
finishingQueue = fq;
robotPool = rp;
}
public Car car() {
return car;
}
public CyclicBarrier barrier() {
return barrier;
}
public void run() {
try {
while (!Thread.interrupted()) {
// Blocks until chassis is available:
car = chassisQueue.take();
// Hire robots to perform work:
robotPool.hire(EngineRobot.class, this);
robotPool.hire(DriveTrainRobot.class, this);
robotPool.hire(WheelRobot.class, this);
barrier.await(); // Until the robots finish
// Put car into finishingQueue for further work
finishingQueue.put(car);
}
} catch (InterruptedException e) {
print("Exiting Assembler via interrupt");
} catch (BrokenBarrierException e) {
// This one we want to know about
throw new RuntimeException(e);
}
print("Assembler off");
}
}
报告任务:
class Reporter implements Runnable {
private CarQueue carQueue;
public Reporter(CarQueue cq) {
carQueue = cq;
}
public void run() {
try {
while (!Thread.interrupted()) {
print(carQueue.take());
}
} catch (InterruptedException e) {
print("Exiting Reporter via interrupt");
}
print("Reporter off");
}
}
机器人基类:
abstract class Robot implements Runnable {
private RobotPool pool;
public Robot(RobotPool p) {
pool = p;
}
protected Assembler assembler;
public Robot assignAssembler(Assembler assembler) {
this.assembler = assembler;
return this;
}
private boolean engage = false;
public synchronized void engage() {
engage = true;
notifyAll();
}
// The part of run() that's different for each robot:
abstract protected void performService();
public void run() {
try {
powerDown(); // Wait until needed
while (!Thread.interrupted()) {
performService();
assembler.barrier().await(); // Synchronize
// We're done with that job...
powerDown();
}
} catch (InterruptedException e) {
print("Exiting " + this + " via interrupt");
} catch (BrokenBarrierException e) {
// This one we want to know about
throw new RuntimeException(e);
}
print(this + " off");
}
private synchronized void
powerDown() throws InterruptedException {
engage = false;
assembler = null; // Disconnect from the Assembler
// Put ourselves back in the available pool:
pool.release(this);
while (engage == false) // Power down
wait();
}
public String toString() {
return getClass().getName();
}
}
安装发动机机器人:
class EngineRobot extends Robot {
public EngineRobot(RobotPool pool) {
super(pool);
}
protected void performService() {
print(this + " installing engine");
assembler.car().addEngine();
}
}
车厢安装工人
class DriveTrainRobot extends Robot {
public DriveTrainRobot(RobotPool pool) {
super(pool);
}
protected void performService() {
print(this + " installing DriveTrain");
assembler.car().addDriveTrain();
}
}
车轮安装工人:
class WheelRobot extends Robot {
public WheelRobot(RobotPool pool) {
super(pool);
}
protected void performService() {
print(this + " installing Wheels");
assembler.car().addWheels();
}
}
机器人池:
class RobotPool {
// Quietly prevents identical entries:
private Set<Robot> pool = new HashSet<Robot>();
public synchronized void add(Robot r) {
pool.add(r);
notifyAll();
}
public synchronized void
hire(Class<? extends Robot> robotType, Assembler d)
throws InterruptedException {
for (Robot r : pool)
if (r.getClass().equals(robotType)) {
pool.remove(r);
r.assignAssembler(d);
r.engage(); // Power it up to do the task
return;
}
wait(); // None available
hire(robotType, d); // Try again, recursively
}
public synchronized void release(Robot r) {
add(r);
}
}
启动:
public class CarBuilder {
public static void main(String[] args) throws Exception {
CarQueue chassisQueue = new CarQueue(),
finishingQueue = new CarQueue();
ExecutorService exec = Executors.newCachedThreadPool();
RobotPool robotPool = new RobotPool();
exec.execute(new EngineRobot(robotPool));
exec.execute(new DriveTrainRobot(robotPool));
exec.execute(new WheelRobot(robotPool));
exec.execute(new Assembler(
chassisQueue, finishingQueue, robotPool));
exec.execute(new Reporter(finishingQueue));
// Start everything running by producing chassis:
exec.execute(new ChassisBuilder(chassisQueue));
TimeUnit.SECONDS.sleep(7);
exec.shutdownNow();
}
} /* (Execute to see output) *///:~
性能调优
Java SE5的并发库添加了大量的用于性能提高的类,在这里说明。
比较各类互斥技术
使用模板方法编写测试用例:
测试模板:
abstract class Accumulator {
public static long cycles = 50000L;
// Number of Modifiers and Readers during each test:
private static final int N = 4;
public static ExecutorService exec =
Executors.newFixedThreadPool(N * 2);
private static CyclicBarrier barrier =
new CyclicBarrier(N * 2 + 1);
protected volatile int index = 0;
protected volatile long value = 0;
protected long duration = 0;
protected String id = "error";
protected final static int SIZE = 100000;
protected static int[] preLoaded = new int[SIZE];
static {
// Load the array of random numbers:
Random rand = new Random(47);
for (int i = 0; i < SIZE; i++)
preLoaded[i] = rand.nextInt();
}
public abstract void accumulate();
public abstract long read();
private class Modifier implements Runnable {
public void run() {
for (long i = 0; i < cycles; i++)
accumulate();
try {
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private class Reader implements Runnable {
private volatile long value;
public void run() {
for (long i = 0; i < cycles; i++)
value = read();
try {
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public void timedTest() {
long start = System.nanoTime();
for (int i = 0; i < N; i++) {
exec.execute(new Modifier());
exec.execute(new Reader());
}
try {
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
duration = System.nanoTime() - start;
printf("%-13s: %13d\n", id, duration);
}
public static void
report(Accumulator acc1, Accumulator acc2) {
printf("%-22s: %.2f\n", acc1.id + "/" + acc2.id,
(double) acc1.duration / (double) acc2.duration);
}
}
测试类:
class BaseLine extends Accumulator {
{
id = "BaseLine";
}
public void accumulate() {
value += preLoaded[index++];
if (index >= SIZE) index = 0;
}
public long read() {
return value;
}
}
class SynchronizedTest extends Accumulator {
{
id = "synchronized";
}
public synchronized void accumulate() {
value += preLoaded[index++];
if (index >= SIZE) index = 0;
}
public synchronized long read() {
return value;
}
}
class LockTest extends Accumulator {
{
id = "Lock";
}
private Lock lock = new ReentrantLock();
public void accumulate() {
lock.lock();
try {
value += preLoaded[index++];
if (index >= SIZE) index = 0;
} finally {
lock.unlock();
}
}
public long read() {
lock.lock();
try {
return value;
} finally {
lock.unlock();
}
}
}
class AtomicTest extends Accumulator {
{
id = "Atomic";
}
private AtomicInteger index = new AtomicInteger(0);
private AtomicLong value = new AtomicLong(0);
public void accumulate() {
// Oops! Relying on more than one Atomic at
// a time doesn't work. But it still gives us
// a performance indicator:
int i = index.getAndIncrement();
value.getAndAdd(preLoaded[i]);
if (++i >= SIZE)
index.set(0);
}
public long read() {
return value.get();
}
}
主函数:
public class SynchronizationComparisons {
static BaseLine baseLine = new BaseLine();
static SynchronizedTest synch = new SynchronizedTest();
static LockTest lock = new LockTest();
static AtomicTest atomic = new AtomicTest();
static void test() {
print("============================");
printf("%-12s : %13d\n", "Cycles", Accumulator.cycles);
baseLine.timedTest();
synch.timedTest();
lock.timedTest();
atomic.timedTest();
Accumulator.report(synch, baseLine);
Accumulator.report(lock, baseLine);
Accumulator.report(atomic, baseLine);
Accumulator.report(synch, lock);
Accumulator.report(synch, atomic);
Accumulator.report(lock, atomic);
}
public static void main(String[] args) {
int iterations = 5; // Default
if (args.length > 0) // Optionally change iterations
iterations = new Integer(args[0]);
// The first time fills the thread pool:
print("Warmup");
baseLine.timedTest();
// Now the initial test doesn't include the cost
// of starting the threads for the first time.
// Produce multiple data points:
for (int i = 0; i < iterations; i++) {
test();
Accumulator.cycles *= 2;
}
Accumulator.exec.shutdown();
}
}
输出结果:
/* Output: (Sample)
Warmup
BaseLine : 34237033
============================
Cycles : 50000
BaseLine : 20966632
synchronized : 24326555
Lock : 53669950
Atomic : 30552487
synchronized/BaseLine : 1.16
Lock/BaseLine : 2.56
Atomic/BaseLine : 1.46
synchronized/Lock : 0.45
synchronized/Atomic : 0.79
Lock/Atomic : 1.76
============================
Cycles : 100000
BaseLine : 41512818
synchronized : 43843003
Lock : 87430386
Atomic : 51892350
synchronized/BaseLine : 1.06
Lock/BaseLine : 2.11
Atomic/BaseLine : 1.25
synchronized/Lock : 0.50
synchronized/Atomic : 0.84
Lock/Atomic : 1.68
============================
Cycles : 200000
BaseLine : 80176670
synchronized : 5455046661
Lock : 177686829
Atomic : 101789194
synchronized/BaseLine : 68.04
Lock/BaseLine : 2.22
Atomic/BaseLine : 1.27
synchronized/Lock : 30.70
synchronized/Atomic : 53.59
Lock/Atomic : 1.75
============================
Cycles : 400000
BaseLine : 160383513
synchronized : 780052493
Lock : 362187652
Atomic : 202030984
synchronized/BaseLine : 4.86
Lock/BaseLine : 2.26
Atomic/BaseLine : 1.26
synchronized/Lock : 2.15
synchronized/Atomic : 3.86
Lock/Atomic : 1.79
============================
Cycles : 800000
BaseLine : 322064955
synchronized : 336155014
Lock : 704615531
Atomic : 393231542
synchronized/BaseLine : 1.04
Lock/BaseLine : 2.19
Atomic/BaseLine : 1.22
synchronized/Lock : 0.47
synchronized/Atomic : 0.85
Lock/Atomic : 1.79
============================
Cycles : 1600000
BaseLine : 650004120
synchronized : 52235762925
Lock : 1419602771
Atomic : 796950171
synchronized/BaseLine : 80.36
Lock/BaseLine : 2.18
Atomic/BaseLine : 1.23
synchronized/Lock : 36.80
synchronized/Atomic : 65.54
Lock/Atomic : 1.78
============================
Cycles : 3200000
BaseLine : 1285664519
synchronized : 96336767661
Lock : 2846988654
Atomic : 1590545726
synchronized/BaseLine : 74.93
Lock/BaseLine : 2.21
Atomic/BaseLine : 1.24
synchronized/Lock : 33.84
synchronized/Atomic : 60.57
Lock/Atomic : 1.79
*///:~
从结果可以发现,程序刚开始时,synchronized关键字性能会优于显式的Lock和Atomic,而当越过门槛后,synchronized关键词会比Lock与Atomic低效很多(编译器会预处理synchronized修饰的值)。
个人结论:当代码非常简单时,使用Atomic既可以保证可读性也能保证性能。
需求可读性的时候使用synchronized(相比Lock的try-finally代码要明了很多),当需要性能调优时使用Lock。
免锁容器
容器是所有编程中的基本工具。旧的Java容器(像Vector
,HashTable)这类使用了大量的synchronized方法,导致非多线程时,导致了不可接受的开销。在Java 1.2时,新的容器类库是不同步的,Collections类提供了各种static的同步的装饰方法,这是个不错的改进,可以选择容器是否需要加锁。
JavaSE5添加了新的容器,通过更灵巧的技术来消除加锁,从而提高线程安全的性能。这些免锁容器背后的通用策略是:
对容器的修改可以与读取操作同时发生,只要读取者只能看到完成修改的结果即可。
CopyOnWriteArrayList,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。当修改完成时,一个原子性的操作将把新的数组换入,使得新的读取操作可以看到这个新的修改。CopyOnWriteArrayList的好处之一是当多个迭代器同事遍历和操作这个列表时,不会抛出ConcurrentModificationException。
CopyOnWriteArraySet将使用CopyOnWriteArrayList来实现其免锁行为。
ConcurrentHashMap和ConcurrentLinkedQueue使用了类似的技术,允许并发的读取和写入,但是容器中只有部分内容而不是整个容器可以被复制和修改。
乐观锁
只要是从免锁容器中读取数据,那么就会比其synchronized对应物快许多,因为获取和释放锁的开销被省掉了。
这里对使用Collections修饰的ArrayList的性能与CopyOnWriteArrayList进行测试。
测试框架:https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/Tester.java
测试任务:https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/ListComparisons.java
/* Output: (Sample)
Type Read time Write time
Synched ArrayList 10r 0w 232158294700 0
Synched ArrayList 9r 1w 198947618203 24918613399
readTime + writeTime = 223866231602
Synched ArrayList 5r 5w 117367305062 132176613508
readTime + writeTime = 249543918570
CopyOnWriteArrayList 10r 0w 758386889 0
CopyOnWriteArrayList 9r 1w 741305671 136145237
readTime + writeTime = 877450908
CopyOnWriteArrayList 5r 5w 212763075 67967464300
readTime + writeTime = 68180227375
*///:~
测试结果,CopyOnWriterArrayList在没有写入,或少量写入时速度要比synchronized ArrayList快出不少。
ConcurrentHashMap与synchronizedHashMap比较:
测试代码:https://github.com/xu509/Java-practise/blob/master/src/main/java/concurrency/MapComparisons.java
测试结果:
/* Output: (Sample)
Type Read time Write time
Synched HashMap 10r 0w 306052025049 0
Synched HashMap 9r 1w 428319156207 47697347568
readTime + writeTime = 476016503775
Synched HashMap 5r 5w 243956877760 244012003202
readTime + writeTime = 487968880962
ConcurrentHashMap 10r 0w 23352654318 0
ConcurrentHashMap 9r 1w 18833089400 1541853224
readTime + writeTime = 20374942624
ConcurrentHashMap 5r 5w 12037625732 11850489099
readTime + writeTime = 23888114831
*///:~
结果中可以发现,ConcurrentHashMap即使写入了大量数据,也不会像CopyOnWriteArrayList那样受影响。因为ConcurrentHashMap使用了一种不同的技术。
乐观加锁
当更新Atomic对象时,使用compareAndSet()方法,他会将旧值和新值一起提交给这个方法,如果与Atomic对象不一致,则阻止提交,于是实现的乐观加锁。
必须仔细考虑当compareAndSet()失败后的操作,如果不能执行回滚操作,那么就不能使用这项技术。
例子:
public class FastSimulation {
static final int N_ELEMENTS = 100000;
static final int N_GENES = 30;
static final int N_EVOLVERS = 50;
static final AtomicInteger[][] GRID =
new AtomicInteger[N_ELEMENTS][N_GENES];
static Random rand = new Random(47);
static class Evolver implements Runnable {
public void run() {
while (!Thread.interrupted()) {
// Randomly select an element to work on:
int element = rand.nextInt(N_ELEMENTS);
for (int i = 0; i < N_GENES; i++) {
int previous = element - 1;
if (previous < 0) previous = N_ELEMENTS - 1;
int next = element + 1;
if (next >= N_ELEMENTS) next = 0;
int oldvalue = GRID[element][i].get();
// Perform some kind of modeling calculation:
int newvalue = oldvalue +
GRID[previous][i].get() + GRID[next][i].get();
newvalue /= 3; // Average the three values
if (!GRID[element][i]
.compareAndSet(oldvalue, newvalue)) {
// Policy here to deal with failure. Here, we
// just report it and ignore it; our model
// will eventually deal with it.
print("Old value changed from " + oldvalue);
}
}
}
}
}
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < N_ELEMENTS; i++)
for (int j = 0; j < N_GENES; j++)
GRID[i][j] = new AtomicInteger(rand.nextInt(1000));
for (int i = 0; i < N_EVOLVERS; i++)
exec.execute(new Evolver());
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}
ReadWriteLock
这个锁能对所有读取的对象放开,只要这些对象不进行写入操作。提高性能的能力完全取决于读取操作与写入操作的比较。
初始化:
// Make the ordering fair:
private ReentrantReadWriteLock lock =
new ReentrantReadWriteLock(true);
写入锁:
public T set(int index, T element) {
Lock wlock = lock.writeLock();
wlock.lock();
try {
return lockedList.set(index, element);
} finally {
wlock.unlock();
}
}
读取锁:
public T get(int index) {
Lock rlock = lock.readLock();
rlock.lock();
try {
// Show that multiple readers
// may acquire the read lock:
if (lock.getReadLockCount() > 1)
print(lock.getReadLockCount());
return lockedList.get(index);
} finally {
rlock.unlock();
}
}
活动对象
Java的线程机制复杂并且难以正确使用,可能是其并发模型继承自过程型语言,应由更适合面向对象的并发模型存在。
其中有一个可替换的方式是 活动对象 或 行动者
每个对象维护着它自己的工作队列和消息队列,并且所有对这个对象的请求都进入队列排队,任何时刻只能运行其中一个。
因此,有了活动对象,我们就可以串行化消息而不是方法,这意味着不再需要防备一个任务在其循环被中断这种问题。
当你向一个活动对象发送消息时,这条消息会转变为一个任务,该任务会被插入到这个对象的队列中,等待在以后的某个时刻运行。
JavaSE5的Future在实现这种模式时将排上用场。下面是一个简单示例:
public class ActiveObjectDemo {
private ExecutorService ex =
Executors.newSingleThreadExecutor();
private Random rand = new Random(47);
// Insert a random delay to produce the effect
// of a calculation time:
private void pause(int factor) {
try {
TimeUnit.MILLISECONDS.sleep(
100 + rand.nextInt(factor));
} catch (InterruptedException e) {
print("sleep() interrupted");
}
}
public Future<Integer>
calculateInt(final int x, final int y) {
return ex.submit(new Callable<Integer>() {
public Integer call() {
print("starting " + x + " + " + y);
pause(500);
return x + y;
}
});
}
public Future<Float>
calculateFloat(final float x, final float y) {
return ex.submit(new Callable<Float>() {
public Float call() {
print("starting " + x + " + " + y);
pause(2000);
return x + y;
}
});
}
public void shutdown() {
ex.shutdown();
}
public static void main(String[] args) {
ActiveObjectDemo d1 = new ActiveObjectDemo();
// Prevents ConcurrentModificationException:
List<Future<?>> results =
new CopyOnWriteArrayList<Future<?>>();
for (float f = 0.0f; f < 1.0f; f += 0.2f)
results.add(d1.calculateFloat(f, f));
for (int i = 0; i < 5; i++)
results.add(d1.calculateInt(i, i));
print("All asynch calls made");
while (results.size() > 0) {
for (Future<?> f : results)
if (f.isDone()) {
try {
print(f.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
results.remove(f);
}
}
d1.shutdown();
}
}
Executors.newSingleThreadExecutor()的调用产生的单线程执行器维护着它自己的无界阻塞队列,只有一个线程从该队列中取走任务并执行知道它们完成。
calculateInt()和calculateFloat()中做的就是通过submit()提交一个新的Callable对象。通过这2个调用可以直接获取Future对象。然后通过Future来发现任务何时完成,并能收集实际的返回值。
使用CopyOnWriteArrayList可以移除为了防止ConcurrentModificationException而复制List的这种需求。
为了能够在不经意间就可以防止线程之间的耦合,任何传递给活动对象方法调用的参数都必须是只读的活动对象,或者不连接对象(即没有连接任何其他任务对象)。
活动对象的优势:
- 每个对象都可以拥有自己的工作器线程
- 每个对象都将维护队它自己的域的全部控制权。
- 所有的活动对象之间的通信都将以这些对象之间的消息形式发生
- 活动对象之间的所有消息都要排队。
由于从一个活动对象到另一个活动对象的消息只能被排队时的延迟所堵塞,并且因为这个延迟总是非常短且独立于任何其他对象的,所以发送消息实际上是不可阻塞的。
由于一个活动对象系统知识经由消息来通信,所以两个对象在竞争调用另一个对象上的方法时,是不会被堵塞的,也就不会发生死锁。
总结
1、可以运行多个独立的任务。
2、必须考虑到这些任务关闭时,可能出现的所有问题。
3、任务可能会在共享资源上彼此干涉。互斥(锁)是用来防止这种冲突的基本工具。
4、如果任务设计得不够仔细,就有可能会死锁、
使用并发的原因:
1、要处理很多任务,他们交织在一起,应用并发能够更有效地使用计算机
2、更好的组织代码
3、更便于用户使用
多线程的主要缺陷有:
1、等待共享资源的时候性能降低
2、需要处理线程的额外CPU花费
3、糟糕的程序设计导致不必要的复杂度
4、有可能产生一些病态行为,如饿死、竞争、死锁和活锁。
5、不同平台导致的不一致性。
扩展阅读:
《Java Concurrency in Practice》
《Concurrent Programming in Java , Second Edition》
《The Java Language Specification , Third Edition》