the implementation of Synchnornized in JVM


快速结论

在JamVM中的实现如下

  • 通过Object中的C结构体的object->lock->monitor实现锁的标志
  • 调度通过pThread的pthread_mutex_lock等操作进行lock与unlock,相当于把最难的调度通过系统调用甩锅给操作系统了
  • Synchnornized是java中互斥锁的实现,用于对并发编程进行支持

Object的线程方法

在了解Java的同步之前,先复习一下Java的线程模型,在Java中,使用Object作为最常用的锁。在Object中,有许多native方法,主要如下

函数功能
wait()释放monitor与时间片,只能超时或者有其他线程notify才能恢复
sleep()释放时间片,但是持有monitor
notify()通知调度器唤醒等待此monitor的BLOCKED线程队列中的一个,转为blocked状态
notifyAll()唤醒所有等待此monitor的线程队列,转为blocked状态

线程的状态有如下几种

source: <http://n.ethz.ch/~anoever/recitation05.pdf>

提醒一下,被notify()调用后是进入了block的队列,需要通过调度器“摇号”挑选其中一个后才能进入同步区块

互斥锁(Synchnornized)

互斥锁在其它编程语言中一般叫做Mutex(|mjuːtex|),用于实现对共享资源的独占性,当进入同步代码块时,获取并独占锁,离开时释放锁。在这段时间中,其它线程访问此资源时,会成为上图的BLOCKED状态。

在Java中线程是通过对C中的pThread包装实现的,pThread接口通过systemcall在内核实现,因此线程的管理本质都是在进行系统调用,有一定的切换消耗,故Synchnornized是一种比较重的锁。

在Java中,Synchnornized可以看作一种语法糖,不需要自己去判断、配置Mutex,只需要用synchnornized(object){}进行包裹即可,接下来我们对这个字段进行一下入门。

1. 对象同步

对象同步中有两种方法,第一种是通过代码块{}的包裹,编译时通过MONITORENTERMONITOREXITSample.this作为锁对象;第二种是为method添加access_flag为synchronized,JVM在执行时将根据FLAG自动加\减锁,如下两种写法在JVM中调用是效果相同的

public class Sample {

  public void do_work() {
    synchronized (Sample.this) {
      //do synchronized work
    }
  }

  public synchronized void do_work() {
    //do synchronized work
  }
}

详见: Java中非static的synchronized方法和synchronized(this)用的是一个锁么?

synchronized在方法中不会被继承,虽然继承synchronized方法有点诡异

再说个题外话,synchronized的锁范围越小越好,不建议放在方法上作为修饰,而是使用synchronized (xxx.this)代码块。common-pool旧版代码中就出现过此问题,导致上游业务出现死锁。

2. 类同步

类同步的对象是Sample.class,以下两种是相同的

public static synchronized do_work(){
    //do synchronized work
}

public void do_work(){
    synchronized (Sample.class){
          //do synchronized work
    }
}

单例的同步方法看这里:http://www.jianshu.com/p/eebcb81b1394

3. synchronized的应用

除了网上用烂的单例同步,多线程for循环自增,并发HashMap等例子外,还可以再举例

3.1. 释放锁与时间片

我曾经在OkHttp中,介绍过Socket的KeepAlive连接自动清理的实现,这里使用了wait进行阻塞,避免无谓的自旋(Spin-waiting)损耗。

while (true) {
  //执行清理并返回下场需要清理的时间
  long waitNanos = cleanup(System.nanoTime());
  if (waitNanos == -1) return;
  if (waitNanos > 0) {
    synchronized (ConnectionPool.this) {
      try {
        //在timeout内释放锁与时间片
        ConnectionPool.this.wait(TimeUnit.NANOSECONDS.toMillis(waitNanos));
      } catch (InterruptedException ignored) {
      }
    }
  }
}

注意wait在Java中一般都是需要在while循环中的,否则你的wait可能被路人线程给wakeup了,上文代码就是Best practice。

3.2. Dead Lock

虽然同步简化了编程,但是还有可能发生死锁,如下例子,当两个不同线程分别调用a.swap(b)b.swap(a)时,在getValue()时可能发生死锁。

//code from <Concurrent Programming in Java>
class Cell {      // Do not use
 private long value;
 synchronized long getValue() { return value; }

 synchronized void setValue(long v) { value = v; }
 synchronized void swapValue(Cell other) {
  long t = getValue();
  long v = other.getValue();
  setValue(v);
  other.setValue(t);
 }
}

Monitor(管程)

重点来了!管程是JVM中的互斥锁的实现,在Java中,Object.wait/notify,以及线程的wait/notify,synchronized生成的字节码MONITORENTER/EXIT等操作在JVM中都是通过Monitor控制与实现的。

在JVM中,每个java.lang.object对象对应的C结构体均拥有一个lock指针,指针对应一个Monitor(也就是说,对象、lock指针与Monitor是一一对应的)。一个Monitor只能同时被一个线程持有。

管程的工作机制如下图

source: http://en.wikipedia.org/wiki/Monitor_(synchronization)

本图来自于维基百科,可以看作是对阻塞式条件变量的一种封装

  • e表示入口队列的线程,它将尝试获取对象的锁,处于blocked状态
  • q表示等待队列的线程,处于waiting状态
  • wait表示调用pThread_wait()进行等待,进入q队列,大多数由开发者手动调用与恢复,下文暂不考虑
  • notified表示调用pThread_notify()后进入e队列
  • enterleave表示独占线程对锁的获取与释放

比如现在有线程队列e同时竞争一个对象,第一个跑的最快的线程enter后,立刻获取到了object的monitor,剩下的线程由于无法获得到monitor,就在e队列中阻塞等待锁的释放。

 enter the monitor:
    enter the method
    if the monitor is locked
        add this thread to e
        block this thread
    else
        lock the monitor

当任务完成后,调用调度器

 leave the monitor:
    schedule
    return from the method

调度器将会释放monitor,并通过某种调度公平的调度策略(可能是FIFO,也可能是优先权值等)将monitor分配给下一个处于blocked态的线程。

  schedule :
    if there is a thread on e
      select and remove one thread from e and restart it
      (this thread will occupy the monitor next)
    else
      unlock the monitor
      (the monitor will become unoccupied)

以Android6.0的vm为例,参考源码如下:

使用Object作为锁好不好?

Java中,通过设置Object结构体中的某个字段映射为锁,所有对象都可以成为锁,这样固然简化了编程,但是相比Ruby等语言使用Mutex作为专用锁,Java所耗用的结构体内存更多

参考

  1. http://android.group.iteye.com/group/wiki/3083-java-sync-communication
  2. http://ibruce.info/2013/12/07/java-interview-questions-concurrency/
  3. https://zh.wikipedia.org/wiki/%E7%9B%A3%E8%A6%96%E5%99%A8_(%E7%A8%8B%E5%BA%8F%E5%90%8C%E6%AD%A5%E5%8C%96)
  4. https://www.ibm.com/developerworks/cn/java/j-threads/