选择显示字体大小

菜鸟初学java的备忘录(七)

我突然发现还有很多东西需要我弄明白,比如synchronized这个关键字的用法.因为在我昨天进行创建连接池套接字的研究的时候,发现假如我不弄清楚这个概念,根本就无法进行下去,所以我决定将自己对socket的兴趣先冷却一下,而回过头来看synchronized.


看了一上午的think in java,觉得还是卓有成效的,应该立即写下来加深印象.我感觉自己的大脑可重用性极低,总是需要生成新的记忆对象,从而耗费许多重复劳动.所以象记录,分析,总结这样类似的工作应该多多益善.


要弄清synchronized的用法,首先要知道它是用来解决什么问题的.既然synchronized是同步的意思,那么它当然就是来解决不同步的问题的.下面就举一个不同步的例子来演示可能出现的问题.

在这个例子当中,我们会创建两个线程类.一个叫twocounter,其工作是对两个计数器变量同时进行累加,从1开始,你马上会想道,我们是要用它来实现一个同步.另一个对象叫watcher,顾名思义,是用来做监视工作的,它负责检查twocounter线程中的两个计数器的值是否相等,看起来这似乎是毫无意义的工作,因为既然是同步累加的,那么两个计数器的值怎么可能不相等呢??

但,事实情况不是这样的.我们先来看程序.在看这个程序之前,最好先翻翻think in java的14.2.1,我的程序实际上是根据该节中给出的例子简化的,其中的主类改作了sharing2


class twocounter extends thread {
  private int count1 = 0, count2 = 0;
  private boolean started=false;
  public void start(){
    if (!started) file://防止多次对一个线程调用start方法
    {
      started=true;
      super.start();
    }
  }
  public void run() {
    while (true) {
      count1++;
file://如果twocounter运行到这个时候,cpu时间片被分配给了watcher,那么这个时候watcher读出来的两个计数器的值当然会不一样了,这个可能性是存在的。“这是由线程的本质造成的——它们可在任何时候挂起(暂停)。所以在上述两行的执行时刻之间,有时会出现执行暂停现象。同时,watcher线程也正好跟随着进来,并正好在这个时候进行比较,造成计数器出现不相等的情况.”(think in java)
      count2++;
      system.out.println("count1="+count1+",count2="+count2);
      try {
        sleep(500);
      } catch (interruptedexception e){}
   }
  }

  public void synchtest() {
    sharing2.incrementaccess();
    if(count1 != count2)
            system.out.println("unsynched");//一旦发现不同步,立即显示
  }
}

class watcher extends thread {
  private sharing2 p;
  public watcher(sharing2 p) {
    this.p = p;
    start();
  }
  public void run() {
    while(true) {
      p.s.synchtest();
      try {
        sleep(500);
      } catch (interruptedexception e){}
    }
  }
}

public class sharing2 {
  twocounter s;
  private static int accesscount = 0;
  public static void incrementaccess() {
    accesscount++;
    system.out.println("accesscount="+accesscount);
  }
  public static void main(string[] args) {
    sharing2 aaa = new sharing2();
    aaa.s=new twocounter();
    aaa.s.start();//打开twocounter线程
    new watcher(aaa);//打开watcher线程
  }
}

上面的注释讲得很清楚了,有可能出现不同步的情况.但奇怪的是,我在运行的时候,却始终没有遇到不同步的情况,那么只有一种情况,就是程序中count1++和count2++几乎是同时进行的,watcher线程插不进来,但是为什么think in java上面的程序运行之后就肯定有不同步的情况呢?两个程序的原理是完全一样的,唯一不同的是我的程序较为简单,并且在命令行下运行,未使用gui.难道是因为使用applet方式运行或者以windows主窗口的方式运行开销更大,使得watcher有机可趁吗?于是我试着在count1++和count2++之间加了一条循环语句,人为的增大空隙,目的是为了让watcher好插进来,造成监测出来的count1不等于count2的情况,实现不同步.修改后的程序是这样的
    
 ......
      count1++;
      for(int i=0;i<5000;i++);
      count2++;
      ......

ok!再运行程序,很快就有不同步现象产生了,这似乎证明我刚才的分析是正确的.但奇怪的是,输出了一次unsynchrized之后,以后就再也没有出现了,也就是说,watcher线程只有一次检测到了两个计数器count不同.这让我觉得有点郁闷,是巧合还是必然呢?也许是时间太短了,等下去肯定还会有unsynchrized输出的.

算了,这个问题先放下来,我们继续.
既然出现了不同步的问题,那很显然,解决的方法就是synchronized:将twocounter的run方法和synchtest方法都变成同步方法.这样做代表什么意思呢? 有什么好处呢?请参考think in java的14.2.2节,里面有非常详尽透彻的阐述.特别是对监视器,也就是我们通常所说的对象锁的概念,书中讲的很清楚.

总之,需要修改的代码如下:
class twocounter extends thread {
  public synchronized void run() {
    while (true) {
      count1++;
      count2++;
      system.out.println(&quot;count1=&quot;+count1+&quot;,count2=&quot;+count2);
      try {
        sleep(500);
      } catch (interruptedexception e){}
   }
  }

  public synchronized void synchtest() {
    sharing2.incrementaccess();
    if(count1 != count2)
            system.out.println(&quot;unsynched&quot;);//一旦发现不同步,立即显示
  }
}
略去其它不写,表示从问题到解决其实很简单,呵呵.
我们注意到无论run()还是synchtest()都是&ldquo;同步的&rdquo;。如果只同步其中的一个方法,那么另一个就可以自由忽视对象的锁定,并可无碍地调用。所以必须记住一个重要的规则:对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized,否则就不能正常地工作。

现在又遇到了一个新问题。watcher2永远都不能看到正在进行的事情,因为整个run()方法已设为&ldquo;同步&rdquo;。而且由于肯定要为每个对象运行run(),所以锁永远不能打开,而synchtest()永远不会得到调用。之所以能看到这一结果,是因为accesscount根本没有变化。


为解决这个问题,我们能采取的一个办法是只将run()中的一部分代码隔离出来。想用这个办法隔离出来的那部分代码叫作&ldquo;关键区域&rdquo;,而且要用不同的方式来使用synchronized关键字,以设置一个关键区域。java通过&ldquo;同步块&rdquo;提供对关键区域的支持;这一次,我们用synchronized关键字指出对象的锁用于对其中封闭的代码进行同步。如下所示:

synchronized(syncobject) {
  // this code can be accessed by only
  // one thread at a time, assuming all
  // threads respect syncobject's lock
}


在能进入同步块之前,必须在synchobject上取得锁。如果已有其他线程取得了这把锁,块便不能进入,必须等候那把锁被释放。
可从整个run()中删除synchronized关键字,换成用一个同步块包围两个关键行,从而完成对sharing2例子的修改。但什么对象应作为锁来使用呢?那个对象已由synchtest()标记出来了&mdash;&mdash;也就是当前对象(this)!所以修改过的run()方法象下面这个样子:

file://注意没有synchronized关键字了
 public void run() {
    while (true) {
      synchronized(this){
        count1++;
        count2++;
      }
      system.out.println(&quot;count1=&quot;+count1+&quot;,count2=&quot;+count2);
      try {
        sleep(500);
      } catch (interruptedexception e){}
   }
  }
file://注意,synchtest()还是要有synchronized关键字的,考虑一下为什么

这样的话,synchtest方法就可以得到调用了,我们也可以看到accesscount的变化了.


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons