菜鸟初学Java的备忘录(八)

类别:Java 点击:0 评论:0 推荐:

2003年1月24日 星期五 晴
我在22号的笔记中不是有一个疑问吗?为什么我编的程序没有不同步的现象产生呢,我把它发到csdn上去了,现在我已经基本解决这个问题了,下面是论坛的回复纪录摘要

回复人:bluesmile979(笑着) ( ) 信誉:100  2003-01-22 21:08:00  得分:0
说说我的看法,我认为最大的问题在于多线程,看了你的代码,好像只有两个线成了。而例子中应该是比较多的线程,多个线程竞争时间片,被打断的几率自然就大得多了。就算你加了循环,由于机器的运算速度,仍然没有多个线程竞争那么现象明显。不知道各位以为如何。

回复人:xm4014(forrest) ( ) 信誉:100  2003-01-22 22:07:00  得分:0
to bluesmile979(笑着)
我也曾经想到过是否因为线程太少而导致,但是我将Think in java的例程中的两个参数都设为1来运行,那么最后也就和我写的程序一样,只有两个线程,结果照样有不同步.这又怎么解释呢

回复人:tianfeichen(侧耳倾听) ( ) 信誉:110  2003-01-22 23:57:00  得分:0
线程的安排毕竟是随机的,很少会有不同步的出现,次数少了不容易发现。
我常用的方法是,先让无限循环开始,循环的时候也不要求输出什么的,只加上一个停止的条件,比如:
if (counter1 != counter2)
{
   System.out.println(counter1 + " ," + counter2)
   System.exit(0);
}
剩下的就是等了,一般十几秒甚至几秒就出结果了,可以发现记数已经到几十万或者几百万了。
如果同时开了5个线程,等了一分钟,我就算是它同步了。

我的方法可能不太科学,不过效果挺好。

回复人: xm4014(forrest) ( ) 信誉:100  2003-01-23 11:44:00  得分:0
可以帮我调试一下吗?为什么我按照你的方法却始终没有得到结果呢?
将下面的代码直接拷贝就可以了,程序名为Sharing2.java,版本是1.4.1
class TwoCounter extends Thread {
  private int count1 = 0, count2 = 0;
  private boolean started=false;
  public void start(){
    if (!started)
    {
      started=true;
      super.start();
    }
  }
  public void run() {
    while (true) {
        count1++;
        count2++;
//      System.out.println("Count1="+count1+",Count2="+count2);
      try {
        sleep(500);
      } catch (InterruptedException e){System.out.println("TwoCounter.run");}
   }
  }

  public void synchTest() {
//    Sharing2.incrementAccess();
    if(count1 != count2)
      {System.out.println(count1+","+count2);
       System.exit(0);
      }
  }
}

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){System.out.println("Watcher.run");}
    }
  }
}

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();
    new Watcher(aaa);
  }
} ///:~

另外,根据你的意思,我的程序是没有问题的,只是线程少了,不同步很难产生,要等到counter增加到很大数目的时候才有可能,对吗?

回复人: hey_you(Hey) ( ) 信誉:100  2003-01-23 13:27:00  得分:0

我是这样想的:不同步而发生冲突是一种可能性,而sychronize是让这种可能性为0。你没有1发现不同步,并不能证明永远都不会发生不同步的情况,那只是一个时间问题。系统对线程的调度受了环境的影响,要是你机器上同时还跑了很多程序,可能情况就不同了。

回复人: xm4014(forrest) ( ) 信誉:100  2003-01-23 15:56:00  得分:0
呵呵,我用tianfeichen(侧耳倾听)的方法运行的程序,也就是我上面贴的代码居然有结果了,counter1= 217327,counter2=217356,还真想差的不少。但是时间上绝不是一两分钟那么简单,至少过了两个小时,可能真是我和他的运行环境的不同造成的.正如hey_you(Hey)所说,只是一个时间问题.

希望其它人能给出更多的看法,如果觉得没必要再讨论下去,那我就接贴.

回复人: bluesmile979(笑着) ( ) 信誉:100  2003-01-23 16:38:00  得分:0
我考,一两个小时你也能坚持,服了。

我认为问题结果也就两点了。一个就是我认为的线程数量

另一个就是你认为的setText会有比较多的处理,占用比较多的资源。

两种情况都会影响到这个问题的出现几率:)楼主宰总结一下吧,呵呵。

回复人: linliangyi(蓝山咖啡) ( ) 信誉:100  2003-01-23 17:10:00  得分:0
sleep(500)占用的时间胜过for(5000)的时间,因此线程在sleep中被切换的概率远胜于在for中被中断的概率!!(回头去看我的程序就知道了)

事实上,两个变量从不相等变为相等,正是说明了不同步!!

顺便说一下关于swing和awt控件在线程中操作时,比如setText,常造成很多意外
楼主可以看看相关的书籍!!

回复人: xm4014(forrest) ( ) 信誉:100  2003-01-24 14:25:00  得分:0
我将各位的观点综合起来总结一下:

首先要肯定的是,假如不使用synchronized关键字来定义同步方法或者定义同步块,那么,发生不同步的可能是绝对存在的,反过来说,synchronized就是让这种可能性为0.

在第一种情况下,发生不同步的可能虽然存在,但是它的几率受到以下几个方面因素的影响
1.在不同的操作系统及运行环境下,捕捉到不同步的几率可能就不一样,或者说等待的时间可能就有长有短
2.程序中线程数目的多寡,如果线程太少,那么这种不同步就难于捕捉到,可能需要等待很长的时间
3.代码本身的影响.比如使用awt类中涉及到GUI的方法,可能就会占用较多的资源,造成很多意外,那么发生冲突的可能性就大得多
4.线程是由操作系统随机分配的,本来就存在着不确定性,这种不确定性也会影响最后的结果

不知道是否正确,大家还有什么补充呢?
明天正式结帖

不过说实话,我有点搞不懂,为什么最后的结果,counter1(217327)和counter2(217356)会相差那么多呢.按照我的程序,即便watcher线程插到两个自加的语句中间来,检测到的这两个计数器之间的差异顶多也就是1啊.出现这么大的差异,只可能是某一个计数器的自加语句有好多次在根本没有运行的情况下就被强行中断了.这就太恐怖了!虽然有其它线程的存在会干扰当前线程,但是也不至于让当前线程语句不运行吧,最多也就是等等再运行啊?我有点糊涂了,操作系统没学好,如果大家不嫌麻烦,清帮我解释一下吧

结果现在又有新的问题,我想又要等到明天才有答案吧

但我们今天可以解决另一个涉及到synchronized的问题.这是我在论坛上看到的一个贴子.正是因为我解决不了,我才认为有必要回头来好好研究线程和同步等内容的.
问题如下:

file://分析这段程序,并解释一下,着重讲讲synchronized、wait(),notify 谢谢!
class ThreadA
{
  public static void main(String[] args)
  {
    ThreadB b=new ThreadB();
    b.start();
    System.out.println("b is start....");
    synchronized(b)//括号里的b是什么意思,起什么作用?
    {
      try
      {
 System.out.println("Waiting for b to complete...");
 b.wait();//这一句是什么意思,究竟让谁wait?
        System.out.println("Completed.Now back to main thread");
      }catch (InterruptedException e){}
    }
    System.out.println("Total is :"+b.total);
   }
}


class ThreadB extends Thread
{
  int total;
  public void run()
  {
    synchronized(this)
    {
      System.out.println("ThreadB is running..");
      for (int i=0;i<100;i++ )
      {
        total +=i;
        System.out.println("total is "+total);
      }
      notify();
    }
  }
}

要分析这个程序,首先要理解notify()和wait(),为什么在前几天纪录线程的时候没有纪录这两个方法呢,因为这两个方法本来就不属于Thread类,而是属于最底层的object基础类的,也就是说不光是Thread,每个对象都有notify和wait的功能,为什么?因为他们是用来操纵锁的,而每个对象都有锁,锁是每个对象的基础,既然锁是基础的,那么操纵锁的方法当然也是最基础了.

 再往下看之前呢,首先最好复习一下Think in Java的14.3.1中第3部分内容:等待和通知,也就是wait()和notify了.

按照Think in Java中的解释:"wait()允许我们将线程置入“睡眠”状态,同时又“积极”地等待条件发生改变.而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒,并检查条件是否有变."

  我们来解释一下这句话.
  "wait()允许我们将线程置入“睡眠”状态",也就是说,wait也是让当前线程阻塞的,这一点和sleep或者suspend是相同的.那和sleep,suspend有什么区别呢?

   区别在于"(wait)同时又“积极”地等待条件发生改变",这一点很关键,sleep和suspend无法做到.因为我们有时候需要通过同步(synchronized)的帮助来防止线程之间的冲突,而一旦使用同步,就要锁定对象,也就是获取对象锁,其它要使用该对象锁的线程都只能排队等着,等到同步方法或者同步块里的程序全部运行完才有机会.在同步方法和同步块中,无论sleep()还是suspend()都不可能自己被调用的时候解除锁定,他们都霸占着正在使用的对象锁不放.
   而wait却可以,它可以让同步方法或者同步块暂时放弃对象锁,而将它暂时让给其它需要对象锁的人(这里应该是程序块,或线程)用,这意味着可在执行wait()期间调用线程对象中的其他同步方法!在其它情况下(sleep啊,suspend啊),这是不可能的.
   但是注意我前面说的,只是暂时放弃对象锁,暂时给其它线程使用,我wait所在的线程还是要把这个对象锁收回来的呀.wait什么?就是wait别人用完了还给我啊!
   好,那怎么把对象锁收回来呢?
   第一种方法,限定借出去的时间.在wait()中设置参数,比如wait(1000),以毫秒为单位,就表明我只借出去1秒中,一秒钟之后,我自动收回.
   第二种方法,让借出去的人通知我,他用完了,要还给我了.这时,我马上就收回来.哎,假如我设了1小时之后收回,别人只用了半小时就完了,那怎么办呢?靠!当然用完了就收回了,还管我设的是多长时间啊.

   那么别人怎么通知我呢?相信大家都可以想到了,notify(),这就是最后一句话"而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒"的意思了.
   因此,我们可将一个wait()和notify()置入任何同步方法或同步块内部,无论在那个类里是否准备进行涉及线程的处理。而且实际上,我们也只能在同步方法或者同步块里面调用wait()和notify().

   这个时候我们来解释上面的程序,简直是易如反掌了.

   synchronized(b){...};的意思是定义一个同步块,使用b作为资源锁。b.wait();的意思是临时释放锁,并阻塞当前线程,好让其他使用同一把锁的线程有机会执行,在这里要用同一把锁的就是b线程本身.这个线程在执行到一定地方后用notify()通知wait的线程,锁已经用完,待notify()所在的同步块运行完之后,wait所在的线程就可以继续执行.

本文地址:http://com.8s8s.com/it/it18022.htm