讲一瞎背景故事:创建并启动两个任务,一个用来向账户中存款,另一个从同一账户中提款。当提款的数额大于账户的当前余额时,提款线程必须等待。不管什么时候,只要向账户新存入一笔资金,存储线程必须通知提款线程重新常识。如果余额扔未达到提款的数额,提款线程必须继续等待新的存款。
再贴上一些相关的API图片
对于Condition的了解,我也不是很深。我觉得就是进程之间通信的条件吧。
再加上关于Java显式锁的API吧。
为了良心上过得去,加上这个。
这个是缀重要的。
代码加上注释了。我就不冗长的解释了。
1 package thread; 2 3 import java.util.concurrent.*; 4 import java.util.concurrent.locks.*; 5 6 public class ThreadCooperation { 7 private static Account account = new Account(); 8 9 public static void main(String[] args) {10 ExecutorService executor = Executors.newFixedThreadPool(2);11 12 System.out.println("Thread 1\t\tThread 2\t\tBalance");13 executor.execute(new DepositTask());//存钱任务14 executor.execute(new WithdrawTask());//取钱任务15 executor.shutdown();16 }17 18 public static class DepositTask implements Runnable {19 @Override20 public void run() {21 try{22 while(true) {23 account.deposit((int)(Math.random() * 10) + 1);24 //如果不加这个休眠的话,电脑的运算速度会教你做人的。25 Thread.sleep(1000);26 }27 } catch(Exception ex) {28 ex.printStackTrace();29 }30 }31 }32 33 public static class WithdrawTask implements Runnable {34 @Override35 public void run() {36 while(true) {37 account.withdraw((int)(Math.random() * 10) + 1);//疯狂取钱38 }39 }40 }41 42 private static class Account {43 private static Lock lock = new ReentrantLock();//锁 没毛病44 private static Condition newDeposit = lock.newCondition();45 private int balance = 0;//余额46 47 public int getBalance() {48 return balance;49 }50 51 public void withdraw(int amount) { //取钱52 lock.lock();53 //良好的编程习惯 lock之后 try...catch...finally里面解锁54 try{55 while(balance < amount) {56 System.out.println("\t\t\tWait for a deposit.");57 newDeposit.await();//没钱就等着58 }59 //当余额不足时,等下一笔存款到账,取钱的任务等待。60 balance -= amount;61 System.out.println("\t\t\tWithdraw " + 62 amount + "\t\t" + getBalance());63 64 } catch (InterruptedException ex) {65 ex.printStackTrace();66 } finally {67 lock.unlock();68 }69 }70 71 public void deposit(int amount) { //存钱72 lock.lock();73 try{74 balance += amount;75 System.out.println("Deposit " + 76 amount + "\t\t\t\t\t" + getBalance());77 78 //newDeposit.signalAll();79 newDeposit.signal();80 } catch (IllegalMonitorStateException e) {81 e.printStackTrace();82 } finally {83 lock.unlock();84 }85 }86 }87 }
补充:
看了书后面又有一个问题,问的是 55~57行代码
while 关键字 换成if可不可以???
运行了一下,发现是不可以的。
这是为什么呢???
当存入一笔money之后,就会执行这里之后的代码。然而,我们判断balance<amount 用的是if 所以不会判断你存入money 之后 余额和要取钱的关系。所以就会透支了。
简单的说,我要取100块钱,account里面的balance只有一毛钱。所以只能等待,等下一笔钱存入。当下一笔钱存入之后,我没有判断,现在account里面的balance。(万一我又存了一毛呢???)。 就执行了balance -= amount; 所以透支了。
生产者 / 消费者
继续瞎讲:假设使用缓冲区存储整数。缓冲区的大小是受限的。缓冲区提供write(int) 方法将一个int 值添加到缓冲区中,还提供方法read() 从缓冲区中读取和删除一个int值。为了同步这个操作,使用具有两个条件的锁,notEmpty(缓冲区非空) 和 notFul(缓冲区未满)。
当任务向缓冲区添加一个int时, 如果缓冲区是满的,那么任务将会等待notFull条件。当任务从缓冲区中读取一个int时,如果缓冲区是空的,那么任务将等待notEmpty条件。
——《Java语言程序设计 进阶篇》
我的分析:我觉的书上是有点逆向思维的意思。我的理解notEmpty是对于消费者来说的, 生产者把int 放到buffer里面 就会告诉消费者notEmpty.singal(); 当消费者要去一个空的buffer取值时,notEmpty就是false 就是一个假命题,所以要告诉消费者等待(notEmpty.await())。
同理,我再叨叨一遍生产者的notFull。生产者嘛,就是不停地生产int 放到buffer里面。生产者是个好同志,他每天忙于生产。怎样才能让他休息呢?很简单,buffer满了生产者就没有办法生产了(再生产就没地方放了,生产个锤子)。这个时候notFill.await()就让生产者休息。 那什么时候再工作呢?只要buffer有空间就生产。当消费者取走了一个int时,消费者不是小偷,取走了一个int要跟生产这说一声:“Buffer is notFull(notFull.signal()).” 这时候勤劳的生产者同志就开始生产了。
配合着上面的图,我想应该能理解的更充分。这样代码应该能理解了。
1 package thread; 2 3 import java.util.LinkedList; 4 import java.util.concurrent.*; 5 import java.util.concurrent.locks.*; 6 7 public class ConsumerProducer { 8 private static Buffer buffer = new Buffer();//缓冲 9 10 public static void main(String[] args) {11 ExecutorService executor = 12 Executors.newFixedThreadPool(2);//两个线程13 executor.execute(new ProducerTask());//生产者14 executor.execute(new ConsumerTask());//消费者15 executor.shutdown();16 }17 18 private static class ProducerTask implements Runnable {19 @Override20 public void run() {21 try{22 int i = 1;23 while(true) {24 System.out.println("Producer writes " + i);25 buffer.write(i++);26 Thread.sleep((int)(Math.random() * 4500));27 }28 } catch(InterruptedException ex) {29 ex.printStackTrace();30 }31 }32 }33 34 private static class ConsumerTask implements Runnable {35 @Override36 public void run() {37 try{38 while (true) {39 System.out.println("\t\t\tConsumer reads " + buffer.read());40 Thread.sleep((int)(Math.random() * 5000));41 }42 } catch(InterruptedException ex) {43 ex.printStackTrace();44 }45 }46 }47 48 private static class Buffer {49 private static final int CAPACITY = 1;//buffer size50 private LinkedListqueue =51 new LinkedList<>();52 53 private static Lock lock = new ReentrantLock();54 55 private static Condition notEmpty = lock.newCondition();//告诉消费者buffer不空56 private static Condition notFull = lock.newCondition(); //告诉生产者buffer不是满的57 58 public void write(int value) { //生产者写59 lock.lock();60 try {61 while(queue.size() == CAPACITY) {62 System.out.println("Wait for notFull condition");63 notFull.await();//buffer满了 就等一等 等到notFull再生产64 }65 66 queue.offer(value);67 System.out.println(value + " in");//这个可以注释 测试用的 很有用68 notEmpty.signal();//既然生产了,就可以告诉消费者 消费了。69 } catch(InterruptedException ex){70 71 } finally {72 lock.unlock();73 }74 }75 76 public int read() { //消费者读77 int value = 0;78 lock.lock();79 try {80 while (queue.isEmpty()) {81 System.out.println("\t\t\tWait for notEmpty condition");82 notEmpty.await(); //空了 就告诉自己等一下,别吃了。83 }84 value = queue.remove();85 System.out.println(value + " out");//这个可以注释 测试用的 很有用86 notFull.signal();//消费了一个 告诉生产者 可以生产了。87 } catch (InterruptedException ex) {88 ex.printStackTrace();89 } finally {90 lock.unlock();91 }92 return value;93 }94 }95 }