这篇主要学习多个任务一起工作去解决某个问题.为了实现这种方式,我们使用了相同的基础特性:互斥.
1.wait()与notifyAll()
wait()和sleep()的区别是:在调用sleep()和yield()的时候锁并没有被释放,而在wait()期间对象锁是释放的.
这里使用生产者和消费者的例子.如下:
//食物
class Meal {
//食物编号
private final int orderNum;
public Meal(int orderNum) { this.orderNum = orderNum; }
public String toString() { return "Meal " + orderNum; }
}
//服务员的任务
class WaitPerson implements Runnable {
//餐厅对象
private Restaurant restaurant;
public WaitPerson(Restaurant r) { restaurant = r; }
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
//判断餐厅是否还有食物
while(restaurant.meal == null)
//没食物,等待
wait(); // ... for the chef to produce a meal
}
System.out.println("Waitperson got " + restaurant.meal);
synchronized(restaurant.chef) {
//食物为空,唤醒餐厅厨师做菜
restaurant.meal = null;
restaurant.chef.notifyAll(); // Ready for another
}
}
} catch(InterruptedException e) {
System.out.println("WaitPerson interrupted");
}
}
}
//餐厅厨师
class Chef implements Runnable {
//餐厅
private Restaurant restaurant;
private int count = 0;
public Chef(Restaurant r) { restaurant = r; }
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
//如果餐厅有食物,等待
while(restaurant.meal != null)
wait(); // ... for the meal to be taken
}
//如果做了10份食物,关闭所有线程,退出
if(++count == 10) {
System.out.println("Out of food, closing");
restaurant.exec.shutdownNow();
}
System.out.println("Order up! ");
synchronized(restaurant.waitPerson) {
//如果做了食物,唤醒服务员取餐
restaurant.meal = new Meal(count);
restaurant.waitPerson.notifyAll();
}
TimeUnit.MILLISECONDS.sleep(100);
}
} catch(InterruptedException e) {
System.out.println("Chef interrupted");
}
}
}
public class Restaurant {
Meal meal;
ExecutorService exec = Executors.newCachedThreadPool();
WaitPerson waitPerson = new WaitPerson(this);
Chef chef = new Chef(this);
public Restaurant() {
exec.execute(chef);
exec.execute(waitPerson);
}
public static void main(String[] args) {
new Restaurant();
}
}
//output
**Order up!
Waitperson got Meal 1
Order up!
Waitperson got Meal 2
Order up!
Waitperson got Meal 3
Order up!
Waitperson got Meal 4
Order up!
Waitperson got Meal 5
Order up!
Waitperson got Meal 6
Order up!
Waitperson got Meal 7
Order up!
Waitperson got Meal 8
Order up!
Waitperson got Meal 9
Out of food, closing
WaitPerson interrupted
Order up!
Chef interrupted
**
在这里只有一个任务在执行,但在复杂的情况下,可能会有多个任务在某个特定对象锁上等待,以此,在这里使用notifyAll()更加安全.
在这里,wait()方法被while()语句包裹.这是因为,在并发应用中,某个其他的任务可能会在WaitPerson被唤醒时,会突然插足并拿走订单,唯一安全的方式是使用这种方式设计.
2.Lock和Condition对象
Condition对象的await()方法用来挂起一个任务,signal()方法来唤醒一个任务.与notifyAll()相比,signalAll()是更安全的方式.
例子如下:
class Car {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean waxOn = false;
public void waxed() {
lock.lock();
try {
waxOn = true; // Ready to buff
condition.signalAll();
} finally {
lock.unlock();
}
}
public void buffed() {
lock.lock();
try {
waxOn = false; // Ready for another coat of wax
condition.signalAll();
} finally {
lock.unlock();
}
}
public void waitForWaxing() throws InterruptedException {
lock.lock();
try {
while(waxOn == false)
condition.await();
} finally {
lock.unlock();
}
}
public void waitForBuffing() throws InterruptedException{
lock.lock();
try {
while(waxOn == true)
condition.await();
} finally {
lock.unlock();
}
}
}
3.死锁
哲学家就餐问题:
//筷子
public class Chopstick {
//是否被拿起的标志位
private boolean taken = false;
public synchronized
void take() throws InterruptedException {
//如果被拿起了,就等待drop方法的唤醒
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()) {
System.out.println(this + " " + "thinking");
//先思考
pause();
// Philosopher becomes hungry
System.out.println(this + " " + "grabbing right");
//先拿右边的筷子
right.take();
System.out.println(this + " " + "grabbing left");
//再拿左边的筷子
left.take();
System.out.println(this + " " + "eating");
//思考
pause();
//放下右边的筷子
right.drop();
//放下左边的筷子
left.drop();
}
} catch(InterruptedException e) {
System.out.println(this + " " + "exiting via interrupt");
}
}
public String toString() { return "Philosopher " + id; }
}
//执行的任务
public class DeadlockingDiningPhilosophers {
public static void main(String[] args) throws Exception {
//思考的时间
int ponder = 0;
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();
}
}
//output
Philosopher 0 thinking
Philosopher 2 thinking
Philosopher 4 thinking
Philosopher 3 thinking
Press 'Enter' to quit
Philosopher 1 thinking
Philosopher 3 grabbing right
Philosopher 4 grabbing right
Philosopher 2 grabbing right
Philosopher 0 grabbing right
Philosopher 2 grabbing left
Philosopher 4 grabbing left
Philosopher 3 grabbing left
Philosopher 1 grabbing right
Philosopher 2 eating
Philosopher 0 grabbing left
Philosopher 1 grabbing left
Philosopher 3 eating
Philosopher 2 thinking
Philosopher 3 thinking
Philosopher 4 eating
Philosopher 3 grabbing right
Philosopher 2 grabbing right
Philosopher 3 grabbing left
Philosopher 0 eating
Philosopher 4 thinking
Philosopher 1 eating
Philosopher 0 thinking
Philosopher 2 grabbing left
Philosopher 0 grabbing right
Philosopher 1 thinking
Philosopher 4 grabbing right
Philosopher 1 grabbing right
Philosopher 0 grabbing left
Philosopher 2 eating
Philosopher 4 grabbing left
Philosopher 3 eating
Philosopher 1 grabbing left
Philosopher 2 thinking
Philosopher 4 eating
Philosopher 3 thinking
Philosopher 4 thinking
Philosopher 0 eating
Philosopher 2 grabbing right
Philosopher 1 eating
Philosopher 0 thinking
Philosopher 1 thinking
Philosopher 4 grabbing right
Philosopher 3 grabbing right
Philosopher 3 grabbing left
Philosopher 4 grabbing left
Philosopher 1 grabbing right
Philosopher 0 grabbing right
Philosopher 2 grabbing left
Philosopher 0 grabbing left
Philosopher 1 grabbing left
最后,死锁了,满足死锁的4个条件如下:
1.互斥条件.
2.至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源.
3.资源不能被任务抢占,任务必须把资源释放当作普通事件.
4.必须有循环等待.
在这里,破坏第四个条件,就不会出现死锁的情况,是其中一个人先左手拿筷子后右手拿筷子.
public class FixedDiningPhilosophers {
public static void main(String[] args) throws Exception {
int ponder = 0;
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();
}
}