这个专题主要讨论并发编程的问题,所有的讨论都是基于JAVA语言的(因其独特的内存模型以及原生对多线程的支持能力),不过本文传达的是一种分析的思路,任何有经验的朋友都能很轻松地将其扩展到任何一门语言。
注:本文的主要参考资料为结城浩所著《JAVA多线程设计模式》。
线程的英文名Thread,原意指“细丝”。在多线程程序中,若要追踪各个线程的轨迹,就会派生出一系列错综复杂的乱线团。假设在运行过程中,如果有人问到“请问现在执行到代码的哪一部分了?”,你需要多个手指头才能指出正确的地方。
当应用程序的规模、复杂程度达到一定程度时,并发设计是一个必将考虑到的问题,以下是一些常见的应用:
至于JAVA中线程的编码方式,无非是继承自抽象类Thread或者实现Runnable接口,想必各位读者都很熟悉了,这里就不复述了。
在多线程程序里,多个线程既然可以自由操作,当然就可能同时操作到同一实例。这个情况又是会造成不必要的麻烦。例如经典的银行取款问题,其“确认可用余款”这一部分的代码应该该为:
if(可用余额大于欲提取金额) { 从可用余额中减掉欲提取金额 }这段逻辑本身并没有问题。先确认可用余额,再检查是否允许提取输入金额,如果系统决定可以领取,则从可用余额中减掉此金额,保证不会发生可用余额变为负数的情况。
但是,同时若有2个以上的线程执行这个程序的代码,可用余额可能会变成负数。比如:
初始化…… 可用余额 = 1000元 欲提取金额 = 1000元 线程A——可用余额大于欲提取金额? 线程A——是 <<<此时切换成线程B>>> 线程B——可用余额大于欲提取金额? 线程B——是 线程B——从可用余额中减掉欲提取金额 线程B——可用余额变为0元 <<<此时切换成线程A>>> 线程A——从可用余额中减掉欲提取金额 线程A——可用余额变为-1000元我们发现,由于时间差,可能会发生线程B夹在线程A的“确认可用余额”和“减去可用余额”之间的情况,这就会导致出现金额为负数的情况。
JAVA中使用synchronized来实现共享互斥,这就好比十字路口的红绿灯处理一样;当直向行车时绿灯时,另一边的横向车灯一定是红灯。synchronized也采用类似的“交通管制”的方式来实现线程间的互斥。
上述银行存取款的互斥实现如下 public class Bank
{ private int money; private String name; public Bank(String name, int money) { this.money = money; this.name = name; } // 存款 public synchronized void deposit(int m) { money += m; } // 取款 public synchronized void withdraw(int m) { if (money >= m) { money -= m; return true; // 已取款 } else { return false; // 余额不足 } } public String getName() { return name; } }
当一个线程正在执行Bank实例的deposit或withdraw方法时,其他线程就不能执行同一实例的deposit以及withdraw方法。欲执行的线程必须排队等候。
也许会注意到,Bank类里还有一个非synchronized的方法——getName。无论其它线程是否正在执行同一实例的deposit、withdraw或者getName方法,都不妨碍它的执行。
synchronized阻挡的几种使用方式,如下
synchronized局部阻挡:如果需要“管制”的不是整个方法,而是方法的一部分,就使用此类阻挡,代码如下
synchronized(表达式) { …… }
synchronized实例方法阻挡:如果需要“管制”的是整个实例方法,而是方法的一部分,就使用此类阻挡,代码如下
synchronized void method() { …… }
这段代码在功能上与如下代码有异曲同工之妙
void method() { synchronized(this) { …… } }
synchronized类方法阻挡:如果需要“管制”的是类方法,就使用此类阻挡,代码如下
class Something { static synchronized void method() { …… } }
这段代码在功能上与如下代码有异曲同工之妙
class Something { static void method() { synchronized(Something.class) { …… } } }从上面可以看出,synchronized类阻挡其实质是用类对象作为锁定的对象去进行互斥的。
线面讲讲线程的协调。上面所说的是最简单的共享互斥,只要有线程再执行就乖乖地等候;现实工作中,我们需要的往往不止于此,比如: