Java同步和线程交互的理解

Java的线程设计比较简单,可是从操作系统层面的API来理解是,会存在问题。Java提供的线程设计和Unix/Linux系统的API设计思路稍有不同,在理解上也会存在问题。笔者使用Java多年,经常使用Java的多线程,确发现在编写一个测试用例时发现自己并没有深入理解Java的线程机制。本文通过简明扼要的方式简要说明并尝试用图的方式理解Java的多线程机制。
请看图:
Java对象中多线程相关变量示意图
简单看图中文章,应该可以理解Java的同步和线程交互的关键了。
Java的多线程是建立在synchronized关键字和Object对象的wait/notify/notifyAll方法上的。一般情况下,Java的多线程使用synchronized关键字保护竞争性变量或方法,从而实现简单高效的多线程编程模型。考虑使用wait方法时,一般涉及复杂的等待通知机制,比如,生产/消费模型就需要用到该机制。
我们看看上图,安装Java的规范,Java的Object对象上具有一个Monitor(监控器)的变量,这个变量是一个计数器变量(我的理解,规范中没有明确定义是否为一个变量),通过synchronized关键字可以修改该变量。对对象调用synchronized,会给对象的监控器变量加一,如果值为1,当前线程获得对象的唯一使用权,这样就相当于操作系统的互斥(Mutex)原语。如果由多个线程在同一个对象上使用synchronized,那么后面的线程会继续累加计数器,退出同步块时,计数器减一,下一个线程可以获得对象的唯一使用权。这就是synchronized关键字的作用。
再看下方的浅蓝色方块,我叫他等待集合(Wait sets),任何线程在对象上调用wait方法时,会把对象添加到等待集合中。如果对象的监控器计数器为0(没有给该对象加锁),则,抛出IllegalMonitorStateException异常,因此,wait方法的调用必须在synchronized关键字的语句块下执行。
注意:wait方法会给对象的监控器解锁(关键的地方,不然其他线程就没有办法获得对象的锁了),并在返回前(无论是正常返回还是异常)又需要重新获得监控器锁。

synchronized(syncLock){
    syncLock.wait();
    // do somethings....
}

上面代码,wait方法在语句块中,本应该是一个整体操作的,现在线程需要等待一个外部操作完成,才可以继续执行。

注意,由于synchronized关键字是在对象上的等待,如果一个类有多个synchronized的方法,那么所有的方法共享同一个监控器,那么意味着一个方法的阻塞,会阻塞该实例的所有方法,如果是静态类,那么虚拟机内所有调用都必须同步调用任何一个方法。

由于线程会在InterruptException中醒来,Java规范建议wait方法采用循环的写法来处理中断异常。

boolean bFlag = false;
do{
  try{
    syncLock.wait();
    bFlag = true;
  }catch(InterruptException e){
    // continue waiting
  }
}while(!bFlag);

这样,中断时间就不会影响真正需要等待的事件了。当然,如果是自己的业务逻辑中断,就需要更多的逻辑处理了。

本文如有不对的地方,请批评指正。
规范部分翻译链接: Java 同步(Synchronization),等待(wait)通知(notify, notifyall)

发布了95 篇原创文章 · 获赞 63 · 访问量 45万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章