Java并发编程实践 目录
并发编程 04—— 闭锁CountDownLatch 与 栅栏CyclicBarrier
并发编程 06—— CompletionService : Executor 和 BlockingQueue
并发编程 10—— 任务取消 之 关闭 ExecutorService
并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略
并发编程 20—— AbstractQueuedSynchronizer 深入分析
概述
第1 部分 定义
Condition 是一种广义的内置条件队列,接口如下:
public interface Condition { // 造成当前线程在接到信号或被中断之前一直处于等待状态。 void await(); // 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 boolean await(long time, TimeUnit unit); // 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 long awaitNanos(long nanosTimeout); // 造成当前线程在接到信号之前一直处于等待状态。 void awaitUninterruptibly(); // 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 boolean awaitUntil(Date deadline); void signal(); // 唤醒一个等待线程 void signalAll(); // 唤醒所有等待线程 }
一个Condition和一个Lock关联在一起,就像一个条件队列和一个内置锁相关联一样。要创建一个Condition,可以在相关联的Lock上调用Lock.newCondition方法。正如Lock比内置加锁提供了更为丰富的功能,Condition同样比内置条件队列提供了更丰富的功能:在每个锁上可存在多个等待、条件等待可以是可中断的或不可中断的、基于时限的等待,以及公平的或非公平的队列操作。
与内置条件队列不同的是,对于每个Lock,可以有任意数量的Condition对象。Condition对象继承了相关的Lock对象的公平性,对于公平的锁,线程会依照FIFO顺序从Condition.await中释放。
下面程序给出了有界缓存的另一种实现,即使用两个Condition,分别为notFull和notEmpty,用于表示“非满”与“非空”两个条件谓词。当缓存为空时,take将阻塞并等待notEmpty,此时put向notEmpty发送信号,可以解除任何在take中阻塞的线程。
1 /** 2 * 14.11 使用显式条件变量的有界缓存 3 * @ClassName: ConditionBoundedBuffer 4 * @author xingle 5 * @param <T> 6 * @date 2015-2-9 上午11:16:32 7 */ 8 public class ConditionBoundedBuffer<T> { 9 protected final Lock lock = new ReentrantLock(); 10 //条件谓词:notFull (count < items.length) 11 private final Condition notFull = lock.newCondition(); 12 //条件谓词:notEmpty (count > 0) 13 private final Condition notEmpty = lock.newCondition(); 14 private static final int BUFFER_SIZE = 100; 15 @GuardedBy("lock") 16 private final T[] items = (T[]) new Object[BUFFER_SIZE]; 17 @GuardedBy("lock") 18 private int tail,head,count; 19 20 //阻塞并直到:notFull 21 public void put(T x) throws InterruptedException { 22 lock.lock(); 23 try{ 24 while(count == items.length) 25 notFull.await(); 26 items[tail] = x; 27 if(++tail == items.length) 28 tail = 0; 29 ++count; 30 notEmpty.signal(); 31 }finally{ 32 lock.unlock(); 33 } 34 } 35 36 //阻塞并直到:notEmpty 37 public T take() throws InterruptedException{ 38 lock.lock(); 39 try{ 40 while(count==0) 41 notEmpty.await(); 42 T x = items[head]; 43 items[head] = null; 44 if(++head == items.length) 45 head = 0; 46 --count; 47 notEmpty.signal(); 48 return x; 49 }finally{ 50 lock.unlock(); 51 } 52 } 53 54 }
ConditionBoundedBuffer的行为和BoundedBuffer相同,但它对条件队列的使用方式更容易理解——在分析使用多个Condition的类时,比分析一个使用单一内部队列加多个条件队列的类简单得多。通过将两个条件谓词分开并放到两个等待线程集中,Condition使其更容易满足单次通知的需求。signal比singalAll更高效,它能极大地减少在每次缓存操作中发生的上下文切换与锁请求的次数。
第2部分 实例
本示范简单模拟银行帐户的存取款活动,帐户余额大于等于取款金额时允许取款;帐户余额小于1000时允许存款(这与真实业务逻辑不符合,只是技术上需要才如此做的)。
1. 实体Account类
1 /** 2 * 3 * @ClassName: Account 4 * @author xingle 5 * @date 2015-2-9 下午5:54:02 6 */ 7 public class Account { 8 private final Lock lock = new ReentrantLock(); 9 10 // Condition对象 11 private final Condition condDeposit = lock.newCondition(); 12 private final Condition condWithdraw = lock.newCondition(); 13 private int balance; 14 15 public Account(int balance){ 16 this.balance = balance; 17 } 18 19 //取钱 20 public void withdraw(int drawAmount) { 21 lock.lock(); 22 try { 23 //如果账户余额不足,则取现方法阻塞 24 while (balance < drawAmount){ 25 System.out.println("取钱阻塞"); 26 condWithdraw.await(); 27 } 28 //执行取钱 29 balance -= drawAmount; 30 System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount + " 账户余额为:"+ balance); 31 //唤醒存钱线程 32 condDeposit.signal(); 33 } catch (InterruptedException ex) { 34 ex.printStackTrace(); 35 } finally { 36 lock.unlock(); 37 } 38 } 39 40 //存钱 41 public void deposit(int depositAmount){ 42 lock.lock(); 43 try{ 44 //如果账户余额大于1000,存钱方法阻塞 45 while(balance >1000){ 46 System.out.println("存钱阻塞"); 47 condDeposit.await(); 48 } 49 balance += depositAmount; 50 System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount + " 账户余额为:"+ balance); 51 //唤醒取钱线程 52 condWithdraw.signal(); 53 } catch(InterruptedException ex){ 54 ex.printStackTrace(); 55 } 56 finally{ 57 lock.unlock(); 58 } 59 } 60 }
2. 调用类(DepositDrawTest类)
1 /** 2 * 3 * @ClassName: DepositDrawTest 4 * @author xingle 5 * @date 2015-2-10 上午10:38:44 6 */ 7 public class DepositDrawTest { 8 9 public static void main(String[] args) { 10 // 创建一个账户,初始账户余额为0 11 Account acct = new Account(0); 12 new DrawThread("取钱者1", acct, 400).start(); 13 new DrawThread("取钱者2", acct, 800).start(); 14 new DepositThread("存款者甲", acct, 600).start(); 15 new DepositThread("存款者乙", acct, 800).start(); 16 new DepositThread("存款者丙", acct, 400).start(); 17 18 } 19 } 20 21 class DrawThread extends Thread { 22 // 模拟用户账户 23 private Account account; 24 // 每次取数数 25 private int drawAmount; 26 27 public DrawThread(String name, Account account, int drawAmount) { 28 super(name); 29 this.account = account; 30 this.drawAmount = drawAmount; 31 } 32 33 public void run() { 34 for (int i = 0; i < 3; i++) { 35 account.withdraw(drawAmount); 36 try { 37 Thread.sleep(100); 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 } 42 } 43 } 44 45 class DepositThread extends Thread { 46 // 模拟用户账户 47 private Account account; 48 // 每次存钱数 49 private int depositAmount; 50 51 public DepositThread(String name, Account account, int depositAmount) { 52 super(name); 53 this.account = account; 54 this.depositAmount = depositAmount; 55 } 56 57 public void run() { 58 for (int i = 0; i < 3; i++) { 59 account.deposit(depositAmount); 60 try { 61 Thread.sleep(100); 62 } catch (InterruptedException e) { 63 e.printStackTrace(); 64 } 65 } 66 } 67 }
执行结果:
3. 总结
- 如果取款金额大于余额则不让取款,等存款队列继续存钱,余额足够支付时再让取款。
- 如果存款过多(大于1000),则存款不让存了,等取款队列把钱取走,余额降低到1000以下时,可以继续存款。
- 这样就允许多次连续取款(只要帐户有钱),多次连续存款(余额不能大于1000),而不是存款、取款依次调用。
1. Java:多线程,使用同步锁(Lock)时利用Condition类实现线程间通信