Java线程:新特征-条件变量

落日映苍穹つ 2024-02-17 21:16 48阅读 0赞

本文 转载至:http://lavasoft.blog.51cto.com/62575/222536

条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量。但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义。

这里的条件和普通意义上的条件表达式有着天壤之别。

条件变量都实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全。

条件变量的出现是为了更精细控制线程等待与唤醒,在Java5之前,线程的等待与唤醒依靠的是Object对象的wait()和notify()/notifyAll()方法,这样的处理不够精细。

而在Java5中,一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signalAll()方法,又可以唤醒该条件下的等待的线程。有关Condition接口的API可以具体参考JavaAPI文档。

条件变量比较抽象,原因是他不是自然语言中的条件概念,而是程序控制的一种手段。

下面以一个银行存取款的模拟程序为例来揭盖Java多线程条件变量的神秘面纱:

有一个账户,多个用户(线程)在同时操作这个账户,有的存款有的取款,存款随便存,取款有限制,不能透支,任何试图透支的操作都将等待里面有足够存款才执行操作。

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.locks.Condition;
  4. import java.util.concurrent.locks.Lock;
  5. import java.util.concurrent.locks.ReentrantLock;
  6. /**
  7. * Java线程:条件变量
  8. *
  9. * @author leizhimin 2009-11-5 10:57:29
  10. */
  11. public class Test {
  12. public static void main(String[] args) {
  13. //创建并发访问的账户
  14. MyCount myCount = new MyCount("95599200901215522", 10000);
  15. //创建一个线程池
  16. ExecutorService pool = Executors.newFixedThreadPool(2);
  17. Thread t1 = new SaveThread("张三", myCount, 2000);
  18. Thread t2 = new SaveThread("李四", myCount, 3600);
  19. Thread t3 = new DrawThread("王五", myCount, 2700);
  20. Thread t4 = new SaveThread("老张", myCount, 600);
  21. Thread t5 = new DrawThread("老牛", myCount, 1300);
  22. Thread t6 = new DrawThread("胖子", myCount, 800);
  23. //执行各个线程
  24. pool.execute(t1);
  25. pool.execute(t2);
  26. pool.execute(t3);
  27. pool.execute(t4);
  28. pool.execute(t5);
  29. pool.execute(t6);
  30. //关闭线程池
  31. pool.shutdown();
  32. }
  33. }
  34. /**
  35. * 存款线程类
  36. */
  37. class SaveThread extends Thread {
  38. private String name; //操作人
  39. private MyCount myCount; //账户
  40. private int x; //存款金额
  41. SaveThread(String name, MyCount myCount, int x) {
  42. this.name = name;
  43. this.myCount = myCount;
  44. this.x = x;
  45. }
  46. public void run() {
  47. myCount.saving(x, name);
  48. }
  49. }
  50. /**
  51. * 取款线程类
  52. */
  53. class DrawThread extends Thread {
  54. private String name; //操作人
  55. private MyCount myCount; //账户
  56. private int x; //存款金额
  57. DrawThread(String name, MyCount myCount, int x) {
  58. this.name = name;
  59. this.myCount = myCount;
  60. this.x = x;
  61. }
  62. public void run() {
  63. myCount.drawing(x, name);
  64. }
  65. }
  66. /**
  67. * 普通银行账户,不可透支
  68. */
  69. class MyCount {
  70. private String oid; //账号
  71. private int cash; //账户余额
  72. private Lock lock = new ReentrantLock(); //账户锁
  73. private Condition _save = lock.newCondition(); //存款条件
  74. private Condition _draw = lock.newCondition(); //取款条件
  75. MyCount(String oid, int cash) {
  76. this.oid = oid;
  77. this.cash = cash;
  78. }
  79. /**
  80. * 存款
  81. *
  82. * @param x 操作金额
  83. * @param name 操作人
  84. */
  85. public void saving(int x, String name) {
  86. lock.lock(); //获取锁
  87. if (x > 0) {
  88. cash += x; //存款
  89. System.out.println(name + "存款" + x + ",当前余额为" + cash);
  90. }
  91. _draw.signalAll(); //唤醒所有等待线程。
  92. lock.unlock(); //释放锁
  93. }
  94. /**
  95. * 取款
  96. *
  97. * @param x 操作金额
  98. * @param name 操作人
  99. */
  100. public void drawing(int x, String name) {
  101. lock.lock(); //获取锁
  102. try {
  103. if (cash - x < 0) {
  104. _draw.await(); //阻塞取款操作
  105. } else {
  106. cash -= x; //取款
  107. System.out.println(name + "取款" + x + ",当前余额为" + cash);
  108. }
  109. _save.signalAll(); //唤醒所有存款操作
  110. } catch (InterruptedException e) {
  111. e.printStackTrace();
  112. } finally {
  113. lock.unlock(); //释放锁
  114. }
  115. }
  116. }

运行结果:

  1. 李四存款3600,当前余额为13600
  2. 张三存款2000,当前余额为15600
  3. 老张存款600,当前余额为16200
  4. 老牛取款1300,当前余额为14900
  5. 胖子取款800,当前余额为14100
  6. 王五取款2700,当前余额为11400

假如我们不用锁和条件变量,如何实现此功能呢?下面是实现代码:

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. /**
  4. * Java线程:不用条件变量
  5. *
  6. * @author leizhimin 2009-11-5 10:57:29
  7. */
  8. public class Test {
  9. public static void main(String[] args) {
  10. //创建并发访问的账户
  11. MyCount myCount = new MyCount("95599200901215522", 10000);
  12. //创建一个线程池
  13. ExecutorService pool = Executors.newFixedThreadPool(2);
  14. Thread t1 = new SaveThread("张三", myCount, 2000);
  15. Thread t2 = new SaveThread("李四", myCount, 3600);
  16. Thread t3 = new DrawThread("王五", myCount, 2700);
  17. Thread t4 = new SaveThread("老张", myCount, 600);
  18. Thread t5 = new DrawThread("老牛", myCount, 1300);
  19. Thread t6 = new DrawThread("胖子", myCount, 800);
  20. //执行各个线程
  21. pool.execute(t1);
  22. pool.execute(t2);
  23. pool.execute(t3);
  24. pool.execute(t4);
  25. pool.execute(t5);
  26. pool.execute(t6);
  27. //关闭线程池
  28. pool.shutdown();
  29. }
  30. }
  31. /**
  32. * 存款线程类
  33. */
  34. class SaveThread extends Thread {
  35. private String name; //操作人
  36. private MyCount myCount; //账户
  37. private int x; //存款金额
  38. SaveThread(String name, MyCount myCount, int x) {
  39. this.name = name;
  40. this.myCount = myCount;
  41. this.x = x;
  42. }
  43. public void run() {
  44. myCount.saving(x, name);
  45. }
  46. }
  47. /**
  48. * 取款线程类
  49. */
  50. class DrawThread extends Thread {
  51. private String name; //操作人
  52. private MyCount myCount; //账户
  53. private int x; //存款金额
  54. DrawThread(String name, MyCount myCount, int x) {
  55. this.name = name;
  56. this.myCount = myCount;
  57. this.x = x;
  58. }
  59. public void run() {
  60. myCount.drawing(x, name);
  61. }
  62. }
  63. /**
  64. * 普通银行账户,不可透支
  65. */
  66. class MyCount {
  67. private String oid; //账号
  68. private int cash; //账户余额
  69. MyCount(String oid, int cash) {
  70. this.oid = oid;
  71. this.cash = cash;
  72. }
  73. /**
  74. * 存款
  75. *
  76. * @param x 操作金额
  77. * @param name 操作人
  78. */
  79. public synchronized void saving(int x, String name) {
  80. if (x > 0) {
  81. cash += x; //存款
  82. System.out.println(name + "存款" + x + ",当前余额为" + cash);
  83. }
  84. notifyAll(); //唤醒所有等待线程。
  85. }
  86. /**
  87. * 取款
  88. *
  89. * @param x 操作金额
  90. * @param name 操作人
  91. */
  92. public synchronized void drawing(int x, String name) {
  93. if (cash - x < 0) {
  94. try {
  95. wait();
  96. } catch (InterruptedException e1) {
  97. e1.printStackTrace();
  98. }
  99. } else {
  100. cash -= x; //取款
  101. System.out.println(name + "取款" + x + ",当前余额为" + cash);
  102. }
  103. notifyAll(); //唤醒所有存款操作
  104. }
  105. }

运行结果:

  1. 李四存款3600,当前余额为13600
  2. 王五取款2700,当前余额为10900
  3. 老张存款600,当前余额为11500
  4. 老牛取款1300,当前余额为10200
  5. 胖子取款800,当前余额为9400
  6. 张三存款2000,当前余额为11400

结合先前同步代码知识,举一反三,将此例改为同步代码块来实现,代码如下:

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. /**
  4. * Java线程:改为同步代码块
  5. *
  6. * @author leizhimin 2009-11-5 10:57:29
  7. */
  8. public class Test {
  9. public static void main(String[] args) {
  10. //创建并发访问的账户
  11. MyCount myCount = new MyCount("95599200901215522", 10000);
  12. //创建一个线程池
  13. ExecutorService pool = Executors.newFixedThreadPool(2);
  14. Thread t1 = new SaveThread("张三", myCount, 2000);
  15. Thread t2 = new SaveThread("李四", myCount, 3600);
  16. Thread t3 = new DrawThread("王五", myCount, 2700);
  17. Thread t4 = new SaveThread("老张", myCount, 600);
  18. Thread t5 = new DrawThread("老牛", myCount, 1300);
  19. Thread t6 = new DrawThread("胖子", myCount, 800);
  20. //执行各个线程
  21. pool.execute(t1);
  22. pool.execute(t2);
  23. pool.execute(t3);
  24. pool.execute(t4);
  25. pool.execute(t5);
  26. pool.execute(t6);
  27. //关闭线程池
  28. pool.shutdown();
  29. }
  30. }
  31. /**
  32. * 存款线程类
  33. */
  34. class SaveThread extends Thread {
  35. private String name; //操作人
  36. private MyCount myCount; //账户
  37. private int x; //存款金额
  38. SaveThread(String name, MyCount myCount, int x) {
  39. this.name = name;
  40. this.myCount = myCount;
  41. this.x = x;
  42. }
  43. public void run() {
  44. myCount.saving(x, name);
  45. }
  46. }
  47. /**
  48. * 取款线程类
  49. */
  50. class DrawThread extends Thread {
  51. private String name; //操作人
  52. private MyCount myCount; //账户
  53. private int x; //存款金额
  54. DrawThread(String name, MyCount myCount, int x) {
  55. this.name = name;
  56. this.myCount = myCount;
  57. this.x = x;
  58. }
  59. public void run() {
  60. myCount.drawing(x, name);
  61. }
  62. }
  63. /**
  64. * 普通银行账户,不可透支
  65. */
  66. class MyCount {
  67. private String oid; //账号
  68. private int cash; //账户余额
  69. MyCount(String oid, int cash) {
  70. this.oid = oid;
  71. this.cash = cash;
  72. }
  73. /**
  74. * 存款
  75. *
  76. * @param x 操作金额
  77. * @param name 操作人
  78. */
  79. public void saving(int x, String name) {
  80. if (x > 0) {
  81. synchronized (this) {
  82. cash += x; //存款
  83. System.out.println(name + "存款" + x + ",当前余额为" + cash);
  84. notifyAll(); //唤醒所有等待线程。
  85. }
  86. }
  87. }
  88. /**
  89. * 取款
  90. *
  91. * @param x 操作金额
  92. * @param name 操作人
  93. */
  94. public synchronized void drawing(int x, String name) {
  95. synchronized (this) {
  96. if (cash - x < 0) {
  97. try {
  98. wait();
  99. } catch (InterruptedException e1) {
  100. e1.printStackTrace();
  101. }
  102. } else {
  103. cash -= x; //取款
  104. System.out.println(name + "取款" + x + ",当前余额为" + cash);
  105. }
  106. }
  107. notifyAll(); //唤醒所有存款操作
  108. }
  109. }

运行结果:

  1. 李四存款3600,当前余额为13600
  2. 王五取款2700,当前余额为10900
  3. 老张存款600,当前余额为11500
  4. 老牛取款1300,当前余额为10200
  5. 胖子取款800,当前余额为9400
  6. 张三存款2000,当前余额为11400

对比以上三种方式,从控制角度上讲,第一种最灵活,第二种代码最简单,第三种容易犯错。

发表评论

表情:
评论列表 (有 0 条评论,48人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java线特征-条件变量

    Java线程:新特征-条件变量   条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量。但是必须说明,这里的条件是没有实际含义的,仅仅是个

    相关 Java线特征-线

    Java线程:新特征-线程池   Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,