工大后院

 找回密码
 加入后院

扫一扫,访问微社区

QQ登录

只需一步,快速开始

搜索
查看: 2483|回复: 11

JAVA多线程设计模式--Single Threaded Execution Pattern

[复制链接]
发表于 2005-12-10 16:49 | 显示全部楼层 |阅读模式
这种模式,一种形像的比喻是:这是一条独木桥,能通过这座桥的只有一个人。[图一]
Single Threaded Execution Pattern是多线程程序设计的基础。



问题提出:若多个线程都擅自更改实例的状态,实例会丧失安全性。
例子:不使用Single Threaded Execution Pattern的范例。
用到三个类来测试
独木桥类

  1. /**
  2. * Bridge类,用来表示行人要通过的独木桥
  3. */
  4. package single;

  5. public class Bridge {

  6.         private int counter = 0;
  7.         private String name = "nobody";
  8.         private String address = "nowhere";
  9.        
  10.         /**
  11.          * 为通过桥的行人进行统计,并把参数传入的name与address拷贝到对应字段。
  12.          * @param name
  13.          * @param address
  14.          */
  15.         public void pass(String name,String address){
  16.                 this.counter++;
  17.                 this.name = name;
  18.                 this.address = address;
  19.                 check();
  20.         }
  21.        
  22.         /**
  23.          * 显示桥的状态
  24.          */
  25.         public String toString(){
  26.                 return "NO."+counter+":"+name+","+address;
  27.         }
  28.        
  29.         /**
  30.          * 检查桥的状态是否正常,如果行人的第一个字符与出生地的第一个字符不一样
  31.          * 则表示出错,桥会损坏。
  32.          */
  33.         private void check(){
  34.                 if(name.charAt(0)!=address.charAt(0)){
  35.                         System.out.println("******broken******"+toString());
  36.                 }
  37.         }
  38. }
复制代码


用户类

  1. /**
  2. * 用户类,表示不断过桥的行人。
  3. */
  4. package single;

  5. public class UserThread extends Thread {

  6.         private final Bridge bridge;
  7.         private final String myName;
  8.         private final String myAddress;
  9.        
  10.         public UserThread(Bridge bridge,String myName,String myAddress){
  11.                 this.bridge = bridge;
  12.                 this.myName = myName;
  13.                 this.myAddress = myAddress;
  14.         }
  15.        
  16.         /**
  17.          * 覆写父类的run方法,该行人不断地过桥。
  18.          */
  19.         public void run(){
  20.                 System.out.println(myName+" begin");
  21.                 while(true){
  22.                         bridge.pass(myName,myAddress);
  23.                 }
  24.         }

  25. }
复制代码


Main类

  1. /**
  2. * 主函数类,用来创建线程
  3. */
  4. package single;

  5. public class Main {

  6.         /**
  7.          * 创建一条桥,让三个人不断地过桥。为了编程方便,把三个人的姓名与出生地的

  8. 第一个字母设为相同.
  9.          * @param args
  10.          */
  11.         public static void main(String[] args) {
  12.                 // TODO 自动生成方法存根
  13.                 Bridge bridge = new Bridge();
  14.                 new UserThread(bridge,"Apple","America").start();
  15.                 new UserThread(bridge,"Banana","Britain").start();
  16.                 new UserThread(bridge,"Cat","China").start();
  17.         }

  18. }
复制代码


看看运行结果:的确出错了.这次运行中,当第1733157个人过桥时出错了.

  1. Apple begin
  2. Banana begin
  3. ******broken******NO.1733157:Banana,Britain
  4. Cat begin
  5. ******broken******NO.7468948:Banana,Britain
  6. ******broken******NO.8698628:Cat,America
  7. ******broken******NO.12099426:Cat,China
  8. ******broken******NO.13232825:Apple,America
  9. ******broken******NO.15602934:Cat,China
  10. ******broken******NO.28252490:Apple,America
  11. ******broken******NO.29486261:Banana,Britain
  12. ******broken******NO.35275950:Cat,China
  13. ******broken******NO.36112088:Banana,America
  14. ******broken******NO.37137157:Apple,America
复制代码


为什么会出错?
因为pass方法可被三个线程调用,pass方法的几条语句可能是交错依次执行的.
考虑两个线程的情况如下:
线程Apple           线程Banana            this.name值      this.address值
this.counter++;   this.counter++;       (之前的值)       (之前的值)
                         this.name=name;    "Banana"         (之前的值)
this.name=name;                             "Apple"           (之前的值)
this.address=address;                        "Apple"           "America"
                       this.address=address; "Apple"           "Britain"
check();          check();                     "Apple"           "Britain"
                                      ******broken*******

[ Last edited by hjack on 2005-12-10 at 16:54 ]
 楼主| 发表于 2005-12-10 16:53 | 显示全部楼层
解决方案:找出实例状态的不稳定的范围即临界区,对临界区加以防护,使同时执行的线程

保持在只有一条的情况。
修改Bridge类,使它成为线程安全的类.在pass与toString两个方法前面加上关键字

synchronized.
修改过的Bridge类如下:

  1. /**
  2. * Bridge类,用来表示行人要通过的独木桥
  3. */
  4. package single;

  5. public class Bridge {

  6.         private int counter = 0;
  7.         private String name = "nobody";
  8.         private String address = "nowhere";
  9.        
  10.         /**
  11.          * 为通过桥的行人进行统计,并把参数传入的name与address拷贝到对应字段。
  12.          * @param name
  13.          * @param address
  14.          */
  15.         public synchronized void pass(String name,String address){
  16.                 this.counter++;
  17.                 this.name = name;
  18.                 this.address = address;
  19.                 check();
  20.         }
  21.        
  22.         /**
  23.          * 显示桥的状态
  24.          */
  25.         public synchronized String toString(){
  26.                 return "NO."+counter+":"+name+","+address;
  27.         }
  28.        
  29.         /**
  30.          * 检查桥的状态是否正常,如果行人的第一个字符与出生地的第一个字符不一样
  31.          * 则表示出错,桥会损坏。
  32.          */
  33.         private void check(){
  34.                 if(name.charAt(0)!=address.charAt(0)){
  35.                         System.out.println("******broken******"+toString());
  36.                 }
  37.         }
  38. }
复制代码


再执行,则无论等多久都不会出现borken.
Apple begin
Banana begin
Cat begin

为什么可以这样做?
声明为synchronized的方法能够保证同一时间只有一个线程可以执行它.有点类似操作系统里说到的信号量的同步互斥.
当线程Apple执行方法pass时,会获得对象的锁定,线程Banana就会在pass方法的入口处被阻挡下来,直到线程Apple执行完pass方法并解除锁定.线程Banana才可以开始执行pass方法.

[ Last edited by hjack on 2005-12-10 at 16:56 ]
回复

使用道具 举报

发表于 2005-12-10 20:56 | 显示全部楼层
我来支持了。

这个例子举得挺好,就是楼主画的图。。。我看了想笑。哈哈。
回复

使用道具 举报

发表于 2005-12-10 21:05 | 显示全部楼层
是不是toString方法可以不修饰成synchronized呢?

因为toString方法在check方法被invoke,check在pass方法被invoke,而线程类只invoke pass方法,所以pass方法修饰成synchronized不就可以了?因为调用到toString的时候线程肯定得老老实实的排队的。

或者hjack你是出于其他哪方面考虑呢?
回复

使用道具 举报

 楼主| 发表于 2005-12-10 22:18 | 显示全部楼层
呵呵,wool说得有道理,如果invoke可以传递的话,pass方法已是synchronized ,check方法被invoke,而toSting方法是在check里会调用.
我当时是这样想的,check方法没有设置为synchronized ,那么在check里调用toString就可能会出现线程不安全,但按照check是线程安全地访问的话,它里面的函数也应该是线程安全的吧.
回复

使用道具 举报

发表于 2005-12-10 23:32 | 显示全部楼层
Originally posted by hjack at 2005/12/10 14:18:
呵呵,wool说得有道理,如果invoke可以传递的话,pass方法已是synchronized ,check方法被invoke,而toSting方法是在check里会调用.
我当时是这样想的,check方法没有设置为synchronized ,那么在check里调用toString就 ...


嗯。感觉你将toString设置成public(覆盖Object的方法),估计是会被外部类调用到的吧,那保证他是线程安全的还是必要的,所以加synchronized还是有意义的,我这么认为。
回复

使用道具 举报

发表于 2006-4-19 00:31 | 显示全部楼层
这两天在看<<JAVA多线程设计模式>>这本书.关于楼主讨论的这个问题,书的后面习题要求改写Gate类,使找出错误时,counter的值能够更小.我当时不明,直接去看它的答案,它给出的是在check();调用前加上:

  1. this.name = name;
  2. this.address = address;
  3. try
  4. {
  5.        Thread.sleep(1000);
  6. }catch (InterruptedException ie){}
  7. check();
复制代码

结果我等了半天也不见有错误出现.
后来才知道,原来要这样改:

  1. this.name = name;
  2. try
  3. {
  4.        Thread.sleep(1000);
  5. }catch (InterruptedException ie){}
  6. this.address = address;
  7. check();
复制代码
回复

使用道具 举报

发表于 2006-4-19 11:48 | 显示全部楼层
楼上什么意思?看不太明白.
回复

使用道具 举报

发表于 2006-4-19 12:45 | 显示全部楼层
在没有加synchronized前,会有这样的输出

  1. Apple begin
  2. Banana begin
  3. ******broken******NO.1733157:Banana,Britain
  4. Cat begin
  5. ******broken******NO.7468948:Banana,Britain
  6. ******broken******NO.8698628:Cat,America
  7. ******broken******NO.12099426:Cat,China
  8. ******broken******NO.13232825:Apple,America
  9. ******broken******NO.15602934:Cat,China
  10. ******broken******NO.28252490:Apple,America
  11. ******broken******NO.29486261:Banana,Britain
  12. ******broken******NO.35275950:Cat,China
  13. ******broken******NO.36112088:Banana,America
  14. ******broken******NO.37137157:Apple,America
复制代码

如果改动Gate类,则可能输出的NO.*****,(****这个值很小),不用运行到1733157这个值
回复

使用道具 举报

发表于 2006-4-19 15:25 | 显示全部楼层
不懂java,不过楼主举例很好。

很久没上来水了。
回复

使用道具 举报

发表于 2006-4-20 22:23 | 显示全部楼层
原帖由 wool王 于 2006-4-19 11:48 发表
楼上什么意思?看不太明白.


Sorry,我搞错了.我看的那本书是说到Gate类,在这里讨论的应该是Bridge类.
以上只要提到Gate类的都应改为Bridge类.
回复

使用道具 举报

发表于 2006-4-20 23:19 | 显示全部楼层
呵呵,,,怪不得...我看得一头雾水
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入后院

本版积分规则

QQ|Archiver|手机版|小黑屋|广告业务Q|工大后院 ( 粤ICP备10013660号 )

GMT+8, 2025-5-15 00:42

Powered by Discuz! X3.5

Copyright © 2001-2024 Tencent Cloud.

快速回复 返回顶部 返回列表