电脑故障

位置:IT落伍者 >> 电脑故障 >> 浏览文章

多线程编程的设计模式 临界区模式


发布日期:2018/3/26
 

临界区模式 Critical Section Pattern 是指在一个共享范围中只让一个线程执行的模式

它是所有其它多线程设计模式的基础所以我首先来介绍它

把着眼点放在范围上这个模式叫临界区模式如果把作眼点放在执行的线程上这个模式就叫

单线程执行模式

首先我们来玩一个钻山洞的游戏我 Axman朋友 Sager同事 Pentium三个人在八角游乐场

循环钻山洞(KAO减肥训练啊)每个人手里有一个牌子每钻一次洞口的老头会把当前的次序

姓名牌号显示出来并检查名字与牌号是否一致

OK这个游戏的参与者有游乐场老头GeezerPlayer就是我们还有山洞 corrie

public class Geezer {

public static void main(String[] args){

Systemoutprintln(预备开始!);

Corrie c = new Corrie();//只有一个山洞所以生存一个实例后传给多个Player

new Player(Axmanc)start();

new Player(Sagerc)start();

new Player(Pentiumc)start();

}

}

这个类暂时没有什么多说的它是一个Main的角色

public class Player extends Thread{

private final String name;

private final String number;

private final Corrie corrie;

public Player(String nameString numberCorrie corrie) {

thisname = name;

thisnumber = number;

rrie = corrie;

}

public void run(){

while(true){

rrieinto(thisnamethisnumber);

}

}

}

在这里我们把成员字段都设成final的为了说明一个Player一旦构造他的名字和牌号就不能改

简单说在游戏中SagerPentium三个人不会自己偷偷把自己的牌号换了也不会偷偷地去

钻别的山洞如果这个游戏一旦发生错误那么错误不在我们玩家

import javautil*;

public class Corrie {

private int count = ;

private String name;

private String number;

private HashMap lib = new HashMap();//保存姓名与牌号的库

public Corrie(){

libput(Axman);

libput(Sager);

libput(Pentium);

}

public void into(String nameString number){

unt ++;

thisname = name;

thisnumber = number;

if(thislibget(name)equals(number))

test():

}

public String display(){

return unt+: + thisname + ( + thisnumber + );

}

private void test(){

if(thislibget(name)equals(number))

;

//Systemoutprintln(OK: + display());

else

Systemoutprintln(ERR: + display());

}

}

这个类中增加了一个lib的HashMap相当于一个玩家姓名与牌号的库因为明知道Corrie只有一个实例

所以我用了成员对象而不是静态实例只是为了能在构造方法中初始化库中的内容从真正意义中说应

该在一个辅助类中实现这样的数据结构封装的功能如果不提供这个lib那么在check的时候就要用

if(nameequasl(Axman)){

if(!numberequals()) //出错

}

else if

这样复杂的语句如果player大多可能会写到手抽筋所以用一个lib来chcek就非常容象

运行这个程序需要有一些耐心因为即使你的程序写得再差在很多单线程测试环境下也能可是正确的

而且多线程程序在不同的机器上表现不同要发现这个例子的错识可能要运行很长一段时间如果你的

机器是多CPU的那么出现错误的机会就大好多

在我的笔记本上最终出现错误是在分钟以后出现的错误有几钟情况:

: ERR:Axman()

: ERR:Sager()

第一种情况是检查到了错误我的牌号明明是却打印出来而第二种明明没有错误却打印了错误

事实上根据以前介绍的多线程知识不难理解这个例子的错误出现因为into不是线程安全的所以在其中

一个线程执行thisname = Axman;后本来应该执行thisnumner=却被切换到另一个线程中执行

thisnumber=然后又经过不可预知的切换执行其中一个的if(thislibget(name)equals(number))

而出现的错误而在打印这个错误时因为display也不是线程安全的正要打印一个错误的结果时由于

thisname或thisnumber其中一个字段被修改却成了正确的匹配而出现错误

另外还有可能会出现序号颠倒或不对应但这个错误我们无法直观地观察因为你根本不知道哪个序号应该

给哪个Player而序号颠倒则有可能被滚动的屏幕所掩盖

[正确的Critical Section模式的例子]

我们知道出现这些错误是因为Corrie类的方法不是线程安全的那么只要修改Corrie类为线程安全的类就行

其它类则不需要修改上面说过如果出现错误那一定不是我们玩家的事:

import javautil*;

public class Corrie {

private int count = ;

private String name;

private String number;

private HashMap lib = new HashMap();//保存姓名与牌号的库

public Corrie(){

libput(Axman);

libput(Sager);

libput(Pentium);

}

public synchronized void into(String nameString number){

unt ++;

thisname = name;

thisnumber = number;

test();

}

public synchronized String display(){

return unt+: + thisname + ( + thisnumber + );

}

private void test(){

if(thislibget(name)equals(number))

;

//Systemoutprintln(OK: + display());

else

Systemoutprintln(ERR: + display());

}

}

运行这个例子如果你的耐心开着你的机器运行三天吧虽然测试天并不能说明第天没有出错

at least现在的正确性比原来那个没有synchronized 保护的例子要可靠多了!

到这里我们对Critical Section模式的例程有了直观的了解在详细解说这个模式之前请想一下test

方法安全吗?为什么?

上一篇:观察者模式 - 设计模式

下一篇:虚拟代理模式(Virtual Proxy)(图)