论坛首页 Java企业应用论坛

关于synchronized的疑问

浏览 60683 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-04-20  
多线程的处理确实是java中的一个专题,需要深入的研究,以前我在做公司的一个小的中间件的时候,对多线程才稍微了解了一些,总的来说,多线程的处理是比较的复杂,由于并发时可能出现的各种情况都需要进行考虑。
0 请登录后投票
   发表时间:2004-04-21  
凤舞兄的意思我明白,其实事实确实也是如此啊,呵呵。

我上面说的话有点不客气,还请多多海涵,大家有空多多交流,呵呵
0 请登录后投票
   发表时间:2004-04-21  
楼上,客气,客气,有空多交流
0 请登录后投票
   发表时间:2004-05-13  
mochow 写道
jaghuang 写道
我也知道线程是一个很大的学问
但让我烦恼的是虽然知道所谓的线程定义是什么,但和实际联系起来之后总会觉得无法判断什么是线程,该如何使用线程。


贴一下我以前看线程的时候的部分笔记: 
 
在java中,每个对象只有一个相应的monitor,一个mutex,而每一个monitor都可以有多个“doors”可以进入,即,同一个monitor中被守护的代码可以在不同的地方,因为同一个对象可以出现在不同的代码段,只要mutex锁定的对象是同一个,每个入口都用Synchronized关键字表明,当一个线程通过了Synchronized关键字,它就所住了该monitor所有的doors。因此是mutex定义了monitor而不是代码。

为了方便理解monitor的概念举个例子:
    飞机山只有一个洗手间,很多乘客都想进去,但一次只能进去一个人,其他人必须等待,此时有如下对应关系:

   飞机:object
     乘客:各个线程
   洗手间:monitor
    洗手间门上的锁:mutex

因为同步的花销很大,因此要尽量避免:
1,不需要同步原子操作:一个原子操作不会被另一个线程中断,所以原子操作不需要被同步;
2,避免同步,在语言上意味着“不变性”,一个具有不变性的对象的状态在其创建之后就不会改变,因此不变的对象不需要同步;通常频繁的访问一个对象,而不修改它,那么该对象就不需要同步,好的方法是使该对象为不可变的;
3,使用同步包装器,用装饰模式可以在同步与不同步之间选择。

另,java规范中并没有要求wait()方法要作为原子操作实现的,因此,判断是否wait()的条件用while比if要好,因为if虽然在某些java实现中起作用,但是其行为实际是不明确的,用while条件也称spin lock,用其代替if只是为了保险起见。(关于这一点while和if偶理解的不是太明白)


线程是一个基本问题, 有它的问题域, java中显示的实现了一部分, 隐式
的实现了一部分.
就像上文提到的mointer, mutex, 在术语上还有race condition, value condition,semaphore等
java中的Synchronized就是race condition问题,即占先问题, 其中自然
就会有mointer,mutex的概念.
wait(), notify() notifyAll()等实现了 value condition, 即去等待一个条件.
而java中要求wait(), nofity()等必须在一个Synchronized体中也是有它的
隐含意义的.
想向大家推荐一本书 thread primer, 书中虽然讲的主要是PThread, 但基本概念是介绍的相当清楚的, 能够给我们提供一个系统的且扎实的概念体系. 不知这里是否有提供上载的地方, 我可以提供. epubcn上应该也可以找到.
to mochow: 你所说的while(), if()问题, 是一个比较关键的问题.
也就是说, 从代码上看, wait()要写在while(xxx)中,而不是if(xxx)中.
"java规范中并没有要求wait()方法要作为原子操作实现的"这句话我觉得
不够明确,因为所谓的原子操作是通过获得能否获得mutex实现的, 而wait()
自然是要释放mutex的, 否则启不是只能有一个等待吗?
当有多个线程wait时,如果在另一个线程中notifyAll(),那些等待的线程就会重新去争夺mutex,这是你就会发现,如果wait是在while中的话,它在获得
mutex后还会去判断一次条件, 所以说第一个获得mutex的线程在这里应该
是通过的, 而其他的线程就会被再次wait[如果你的程序写对的话], 而如果
是在if中的话,就没有这一步.
应该说在某些场景下, 是可以写if的.[这要看你的实际需要],我们所要
讨论的是: 要知道while和if的区别在那里.
你可以想象一个生产消费问题.
一个线程生产, 多个线程消费.
消费的线程应该会有这么一段
try{
  if(haveNewOne) {wait();}
}catch..
haveNewOne = false;
return 东西;
那我们想象一下如果有多个消费线程在等待,这时有一个生产线程生产
出一个东西并nodifyAll(), 则那些消费的线程就会依次从挂起状态变为运行,
然后都返回那个东西. 如果这是你希望的, 那就没有问题. 可我想大多数的
生产消费问题应该是这样的: 即生产一个消费一个, 一个东西不能被消费多次. 所以这么看来就不对了. 那我们改为while呢, 哇! 那就没有问题了.
除第一个有幸夺得mutex外得线程还要再次等待,而这才是我们所需要得.
0 请登录后投票
   发表时间:2004-05-18  
我来说说我的看法.

关于为什么使用synchronized,是为了在同一时刻,保证只有一个线程在执行同一个代码块。它是通过独占某一个唯一资源来实现互斥的,这个资源就是实例。
同步一般都有如下几个用法:

  1。同步方法
  2。同步静态方法
  3。同步块
 
当声明了一个同步方法 synchronized callXXXX()时,它表明了在进入某对象实例的这个方法时,虚拟机会对该实例执行锁定,并且阻塞没有拥有这个实例的锁的线程。调用该方法可以简化为以下原子操作:

    申请该实例的锁(没有得到则阻塞)
    得到了就继续执行
    执行完毕则释放该实例的锁
   
同理,同步块也和方法同步类似,不过这里不一定是使用所在类的实例锁,可以是其它的对象实例,例:

    synchronized(this){
      ...
    }

也可以是:
    Object obj = new Object();
    ...
    synchronized (obj){
      ...
    }
   
同时要注意以下两种形式是等价的:
   Object obj = new Object();
   ...
   synchronized(obj){
     ...
   }
   =
   Object another = obj;
   synchronized(another){
     ...
   }

值得注意的是,静态方法比较特殊,进入一个同步的静态方法时,由于没有类实例,所以它进行锁定的是类的Class实例  
那么既然只有实例才能上锁,如果想对一个类的所有实例进行同步该如何做呢?可以使用静态成员并在类装载时初始化,也可以利用同步的静态方法。原理其实是一样的:利用String对象和类的Class对象这种唯一性已经被保证了的对象实例来进行锁定,例:
  synchronized("AAA"){
   ...
  }  
 
无论在哪里调用,以上代码区域都会保证同步。 

值得严重关注的一个问题是有关Java语言处理loong和double的方式,不保证多个线程修改同一个loong或double变量时的操作是原子性的(Effective Java P165),这个可要小心了。

至于wait()和notify()& notifyAll(),个人觉得就算不将wait()放在while()中也没有什么不可以的。

那什么时候该用同步,什么时候该不同步呢?很明显的,作为程序保证无错是首要第一任务,而不是性能。而以下原则是最好的解决办法:

   无论何时,当多个线程共享可变数据时,每个读或者写数据的线程必须取得一把锁
0 请登录后投票
   发表时间:2004-05-18  
哈哈,又来了一位高手!
   不过,老方,不是每个人都能准确把握什么地方该用同步,什么地方不该用的。其实ThreadLocal的出现也就是为了解决这样的问题。在http://www-900.cn.ibm.com/developerWorks/cn/java/j-threads/index3.shtml这篇文章中,作者也谈到“编写线程安全类是困难的。它不但要求仔细分析在什么条件可以对变量进行读写,而且要求仔细分析其它类能如何使用某个类。 有时,要在不影响类的功能、易用性或性能的情况下使类成为线程安全的是很困难的。有些类保留从一个方法调用到下一个方法调用的状态信息,要在实践中使这样的类成为线程安全的是困难的。”
0 请登录后投票
   发表时间:2004-05-18  
to wolfsquare:
至于wait()和notify()& notifyAll(),个人觉得就算不将wait()放在while()中也没有什么不可以的。

我想说,这是绝对错误的. 应该说并不一定要求把
wait放在while()中,因为这要和具体的场景,需求联系
在一起. 但要明确的是, 写在if()或while()中是有区别
的, 内涵是不一样的, 如果认为这两者没有差别, 说明
对java中的线程的理解还存在一些偏差.

写在while()中,就相当于做了一个循环校验, 可以保证在多个线程被同时唤醒时(notifyall), 这些线程可以被继续挂起.
0 请登录后投票
   发表时间:2004-05-19  
看来我的确反应比较迟钝,楼上能不能再说明白一点? 比如AB两个不同类型的线程协同合作,
A先挂起等待Z对象的锁释放,B在进入同步后使用Z.wait()释放了该信号,等待A执行某一步骤后再继续执行,那么这时候是否一定要使用while(...)?
0 请登录后投票
   发表时间:2004-05-19  
wait中的线程数太少,当两个以上的线程同时wait某state时,如用if (state),某非wait线程notifyAll,最先获得同步锁的线程顺利执行完毕解锁后并不能保证其他得到锁的线程此时会满足if(state),即state可能已被最先获得同步锁的线程改变.
0 请登录后投票
   发表时间:2004-05-19  
终于想清楚了为什么需要在while(...)中执行wait(),其实是因为wait()了之后不一定会阻塞,如果wait()后没有其它线程Hold住该信号,那么该线程会继续执行。而这个过程是很快的,当争用者线程太少时,几乎很少有机会几乎在同一时间段内完成释放-锁定操作。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics