这个专题主要讨论并发编程的问题,所有的讨论都是基于JAVA语言的(因其独特的内存模型以及原生对多线程的支持能力),不过本文传达的是一种分析的思路,任何有经验的朋友都能很轻松地将其扩展到任何一门语言。

注:本文的主要参考资料为结城浩所著《JAVA多线程设计模式》。

线程的英文名Thread,原意指“细丝”。在多线程程序中,若要追踪各个线程的轨迹,就会派生出一系列错综复杂的乱线团。假设在运行过程中,如果有人问到“请问现在执行到代码的哪一部分了?”,你需要多个手指头才能指出正确的地方。

当应用程序的规模、复杂程度达到一定程度时,并发设计是一个必将考虑到的问题,以下是一些常见的应用:

  • GUI:以word为例,我们正在编辑一份大型的文档,此时执行“查找”操作;当word进行查找时,同时会出现一个“停止查找”的按钮,用户可以随时停止。此时就用到了多线程,其中一个线程在后台执行查找,另一个线程显示“停止查找”的按钮,一旦按下,则立即停止查找。两个操作交由不同的线程来处理,各线程可以专心负责自己的功能,因此也是模块化设计思想的一种体现。
  • 比较耗时的I/O处理:由于磁盘、网络的IO操作消耗的时间远大于内存处理,如果在此段时间内,程序仅仅是等待而无法执行其它处理,性能会大打折扣。如果事先能将I/O处理和非I/O处理区分开来,这样就能够利用进行I/O处理时CPU空闲的间隙,进行其它处理了。
  • 一个Server与多个Client:大部分Server都要求能够同时服务于1个以上的Client。Server本身并不知道何时会有Client接入,并且在Server中直接引入多个Client的设计,并不是十分优雅的方案;因此不妨设计成一旦有Client连接到Server,就会生成自动出来迎接这个Client的线程。这样一来,Server端的程序就可以简单地设计成好像只服务一个Client。当然,从J2SE 1.4开始,已经加入了新的NIO类库,不必利用线程也能进行兼具性能和扩充性的I/O处理,详情可参考JDK。

至于JAVA中线程的编码方式,无非是继承自抽象类Thread或者实现Runnable接口,想必各位读者都很熟悉了,这里就不复述了。

在多线程程序里,多个线程既然可以自由操作,当然就可能同时操作到同一实例。这个情况又是会造成不必要的麻烦。例如经典的银行取款问题,其“确认可用余款”这一部分的代码应该该为:

  1. if(可用余额大于欲提取金额)
  2. {
  3. 从可用余额中减掉欲提取金额
  4. }

这段逻辑本身并没有问题。先确认可用余额,再检查是否允许提取输入金额,如果系统决定可以领取,则从可用余额中减掉此金额,保证不会发生可用余额变为负数的情况。

但是,同时若有2个以上的线程执行这个程序的代码,可用余额可能会变成负数。比如:

  1. 初始化……
  2. 可用余额 = 1000元
  3. 欲提取金额 = 1000元
  4. 线程A——可用余额大于欲提取金额?
  5. 线程A——是
  6. <<<此时切换成线程B>>>
  7. 线程B——可用余额大于欲提取金额?
  8. 线程B——是
  9. 线程B——从可用余额中减掉欲提取金额
  10. 线程B——可用余额变为0元
  11. <<<此时切换成线程A>>>
  12. 线程A——从可用余额中减掉欲提取金额
  13. 线程A——可用余额变为-1000元

我们发现,由于时间差,可能会发生线程B夹在线程A的“确认可用余额”和“减去可用余额”之间的情况,这就会导致出现金额为负数的情况。

JAVA中使用synchronized来实现共享互斥,这就好比十字路口的红绿灯处理一样;当直向行车时绿灯时,另一边的横向车灯一定是红灯。synchronized也采用类似的“交通管制”的方式来实现线程间的互斥。

上述银行存取款的互斥实现如下

  1. public class Bank
  2. {
  3. private int money;
  4. private String name;
  5. public Bank(String name, int money)
  6. {
  7. this.money = money;
  8. this.name = name;
  9. }
  10. // 存款
  11. public synchronized void deposit(int m)
  12. {
  13. money += m;
  14. }
  15. // 取款
  16. public synchronized void withdraw(int m)
  17. {
  18. if (money >= m)
  19. {
  20. money -= m;
  21. return true;  // 已取款
  22. }
  23. else
  24. {
  25. return false; // 余额不足
  26. }
  27. }
  28. public String getName()
  29. {
  30. return name;
  31. }
  32. }

当一个线程正在执行Bank实例的deposit或withdraw方法时,其他线程就不能执行同一实例的deposit以及withdraw方法。欲执行的线程必须排队等候。

也许会注意到,Bank类里还有一个非synchronized的方法——getName。无论其它线程是否正在执行同一实例的deposit、withdraw或者getName方法,都不妨碍它的执行。

synchronized阻挡的几种使用方式,如下 
synchronized局部阻挡:如果需要“管制”的不是整个方法,而是方法的一部分,就使用此类阻挡,代码如下

  1. synchronized(表达式)
  2. {
  3. ……
  4. }

synchronized实例方法阻挡:如果需要“管制”的是整个实例方法,而是方法的一部分,就使用此类阻挡,代码如下

  1. synchronized void method()
  2. {
  3. ……
  4. }

这段代码在功能上与如下代码有异曲同工之妙

  1. void method()
  2. {
  3. synchronized(this)
  4. {
  5. ……
  6. }
  7. }

synchronized类方法阻挡:如果需要“管制”的是类方法,就使用此类阻挡,代码如下

  1. class Something
  2. {
  3. static synchronized void method()
  4. {
  5. ……
  6. }
  7. }

这段代码在功能上与如下代码有异曲同工之妙

  1. class Something
  2. {
  3. static void method()
  4. {
  5. synchronized(Something.class)
  6. {
  7. ……
  8. }
  9. }
  10. }

从上面可以看出,synchronized类阻挡其实质是用类对象作为锁定的对象去进行互斥的。

线面讲讲线程的协调。上面所说的是最简单的共享互斥,只要有线程再执行就乖乖地等候;现实工作中,我们需要的往往不止于此,比如:

  • 若空间有空闲则继续写入,若没有则等候。
  • 空间有空闲时,及时通知等待线程。

线程协调的具体实现将在下一章中介绍。

JAVA中提供了wait、notify、notifyAll以支持此类条件处理。这里要注意到以下几点:

  • 如欲执行某一实例的wait方法,则首先必须获得该实例的锁;一旦进入wait set(线程的休息间),则自动释放该锁。
  • 使用notify方法时,会从锁定实例的wait set中唤醒一个线程。同样的,线程必须首先获得锁定,才能调用notyfy方法。被唤醒的线程并不是立即就可以执行的,因为在此刻,notify的线程还一直占有锁。另外,假设执行notify时,wait set里有多于一个的线程在等待,具体选择那个线程是无法得知的,因此应用程序最好不要写成会因所选线程而有所变动的方式。
  • notifyAll与notify基本相同,唯一区别在于它是唤醒所有线程而非一个。一般来说,使用notifyAll写的程序会更健壮一点。

转载:http://grunt1223.iteye.com/blog/876245

最新文章

  1. js获取当前日期时间格式为“yyyy-MM-dd HH:MM:SS”
  2. asp.net下AjaxMethod的使用方法
  3. Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)
  4. Java 找到数组中两个元素相加等于指定数的所有组合
  5. Oracle Database Links解析
  6. Python网页解析
  7. python中 and 和 or 运算的核心思想 ——— 短路逻辑
  8. Android调试工具之ADB
  9. Jquery判断$(&quot;#id&quot;)获取的对象是否存在
  10. ghithub中PHPOffice/PHPWord的学习
  11. NTP系统时间同步-操作记录
  12. nginx+php+memcache实现hash一致性memcache 集群
  13. shiro中单点登录
  14. [Windows Azure] Windows Azure Storage &amp; SQL Database
  15. JAVA-JSP内置对象之application对象获得其他信息
  16. strcat的几种实现及性能比较
  17. Field &#39;***********&#39; doesn&#39;t have a default value
  18. PDO访问方式操作数据库
  19. centos7的防火墙(firewalld)
  20. 【Python】模块学习之ConfigParser读写配置信息

热门文章

  1. 分类和逻辑回归(Classification and logistic regression)
  2. Octave中调用hist出现broken pipe some output may be lost octave的解决(Mac)
  3. C命令行参数
  4. 使用C#书写SQLite数据库增删改查语句(以及插入byte[]时遇到的问题总结)
  5. @JsonIgnore
  6. knockout的监控数组实现
  7. Spring &lt;context:annotation-config /&gt;讲解
  8. SPI子系统分析之一:框架
  9. 阿里巴巴Java开发规约扫描插件-Alibaba Java Coding Guidelines 在idea上安装使用教程
  10. css实现栅格的方法