synchronized应该是我们最早接触到的java中同步的方法了, 大致有这么几种用法:

 1 public class SuperClass {
 2 
 3     public synchronized void method0() {
 4     }
 5 
 6     public static synchronized void method1() {}
 7 
 8     Object object = new Object();
 9     public void method3() {
10         synchronized (object) {
11         }
12     }
13 
14     public void method4() {
15         synchronized (this) {
16         }
17     }
18 
19     static Object sObject = new Object();
20     public void method5() {
21         synchronized (sObject) {
22         }
23     }
24 
25     public void method6() {
26         synchronized (SuperClass.class) {
27         }
28     }
29 
30 }

具体试验的代码就不贴出来了, 直接总结一下吧:

  • 无论sychronized怎么用,本质上都是获取一个对象的monitor, JVM保证一个对象的monitor只能被一个线程持有,因此保证了互斥

  • sychronized一个实例的方法, 就是获取这个实例对象的monitor

  • sychronized一个类static方法, 就是获取这个类class对象的monitor

  • sychronized一个object, 那就是获取这个object的monitor, 无论它是class对象、普通对象 或者是static成员, 只要是不同的对象或者不同时对这个对象请求monitor控制权,就不存在互斥关系

因此我们看到,所有的互斥操作都是基于Object来完成的,并且多次提到了object’s monitor, 它到底是什么? 先放一边, 我们先看看这一对难兄难弟:

 1 /**
 2      * Causes the current thread to wait until another thread invokes the
 3      * {@link java.lang.Object#notify()} method or the
 4      * {@link java.lang.Object#notifyAll()} method for this object.
 5      * In other words, this method behaves exactly as if it simply
 6      * performs the call {@code wait(0)}.
 7      * <p>
 8      * The current thread must own this object's monitor. The thread
 9      * releases ownership of this monitor and waits until another thread
10      * notifies threads waiting on this object's monitor to wake up
11      * either through a call to the {@code notify} method or the
12      * {@code notifyAll} method. The thread then waits until it can
13      * re-obtain ownership of the monitor and resumes execution.
14      * <p>
15      * As in the one argument version, interrupts and spurious wakeups are
16      * possible, and this method should always be used in a loop:
17      * <pre>
18      *     synchronized (obj) {
19      *         while (&lt;condition does not hold&gt;)
20      *             obj.wait();
21      *         ... // Perform action appropriate to condition
22      *     }
23      * </pre>
24      * This method should only be called by a thread that is the owner
25      * of this object's monitor. See the {@code notify} method for a
26      * description of the ways in which a thread can become the owner of
27      * a monitor.
28      *
29      * @exception  IllegalMonitorStateException  if the current thread is not
30      *               the owner of the object's monitor.
31      * @exception  InterruptedException if any thread interrupted the
32      *             current thread before or while the current thread
33      *             was waiting for a notification.  The <i>interrupted
34      *             status</i> of the current thread is cleared when
35      *             this exception is thrown.
36      * @see        java.lang.Object#notify()
37      * @see        java.lang.Object#notifyAll()
38      */
39     public final void wait() throws InterruptedException {
40         wait(0);
41     }
  • 调用这个方法导致当前线程进入等待(我的理解是进入WAITING/TIME_WAITING状态),直到别的线程调用此对象的notify/notifyAll

  • 当前线程必须持有这个对象的monitor(否则会收到IllegalMonitorsStateException),调用wait后,释放它的所有权,当别的线程调用此对象的notify/notifyAll时,会唤醒此线程,但是它仍要等到能重新获得这个monitor才能继续执行

  • 当设置intrrupt标志位时,会抛出InterruptedException,就像上一篇文章里说的那样


 1 /**
 2      * Wakes up a single thread that is waiting on this object's
 3      * monitor. If any threads are waiting on this object, one of them
 4      * is chosen to be awakened. The choice is arbitrary and occurs at
 5      * the discretion of the implementation. A thread waits on an object's
 6      * monitor by calling one of the {@code wait} methods.
 7      * <p>
 8      * The awakened thread will not be able to proceed until the current
 9      * thread relinquishes the lock on this object. The awakened thread will
10      * compete in the usual manner with any other threads that might be
11      * actively competing to synchronize on this object; for example, the
12      * awakened thread enjoys no reliable privilege or disadvantage in being
13      * the next thread to lock this object.
14      * <p>
15      * This method should only be called by a thread that is the owner
16      * of this object's monitor. A thread becomes the owner of the
17      * object's monitor in one of three ways:
18      * <ul>
19      * <li>By executing a synchronized instance method of that object.
20      * <li>By executing the body of a {@code synchronized} statement
21      *     that synchronizes on the object.
22      * <li>For objects of type {@code Class,} by executing a
23      *     synchronized static method of that class.
24      * </ul>
25      * <p>
26      * Only one thread at a time can own an object's monitor.
27      *
28      * @exception  IllegalMonitorStateException  if the current thread is not
29      *               the owner of this object's monitor.
30      * @see        java.lang.Object#notifyAll()
31      * @see        java.lang.Object#wait()
32      */
33     public final native void notify();
34 
35 
36 
37 
38 
39     /**
40      * Wakes up all threads that are waiting on this object's monitor. A
41      * thread waits on an object's monitor by calling one of the
42      * {@code wait} methods.
43      * <p>
44      * The awakened threads will not be able to proceed until the current
45      * thread relinquishes the lock on this object. The awakened threads
46      * will compete in the usual manner with any other threads that might
47      * be actively competing to synchronize on this object; for example,
48      * the awakened threads enjoy no reliable privilege or disadvantage in
49      * being the next thread to lock this object.
50      * <p>
51      * This method should only be called by a thread that is the owner
52      * of this object's monitor. See the {@code notify} method for a
53      * description of the ways in which a thread can become the owner of
54      * a monitor.
55      *
56      * @exception  IllegalMonitorStateException  if the current thread is not
57      *               the owner of this object's monitor.
58      * @see        java.lang.Object#notify()
59      * @see        java.lang.Object#wait()
60      */
61     public final native void notifyAll();
  • 唤醒一个等待此对象monitor(调用wait)的线程,但不保证是哪一个,我的理解就是将线程从WAITING状态恢复到BLOCKED状态

  • 被唤醒的那个线程还要去跟别的线程(同样synchronized这个object)竞争获得这个monitor的控制权,拿到了才能恢复为RUNNABLE状态

  • 调用此方法的先决条件是当前线程要持有这个对象的monitor

  • notifyAll跟notify的差别就是它会唤醒所有等待这个monitor的线程

那么为什么不推荐用notify呢, 因为唤醒一个线程后并且它争取到了这个monitor,但是由于条件不满足而重新进入wait(见上面wait注释中的代码, 至于为什么有可能条件不满足,自己想吧,这也是为什么synchronized不够精细而祭出Lock+Condition的原因吧)

终极Boss: object’s Monitor

先问一个问题,为啥线程需要互斥和同步?
这涉及到jvm的内存模型,一个进程中,所有线程是共享堆的,所有的对象的new操作全部在堆上完成,这就意味着所有线程都能访问到堆中的同一对象,因此需要保证访问的互斥、同步关系

什么是monitor, wiki上大致是这样说的:

  • monitor是java提供的一种对锁的封装

  • monitor可以保证所有线程互斥的获得一个锁

  • monitor可以让出对锁的权限并且等待一个条件去重新获得这个锁

  • monitor可以发出信号通知别的线程他们等待的条件被满足了

来给一个monitor的执行序列图吧: pic1

那么到底到底,monitor怎么实现的呢?
还是wiki了一下https://en.wikipedia.org/wiki/Monitor_%28synchronization%29
可以通过我们学过的操作系统中的mutex和semaphore来实现,前者保证同步,后者保证互斥,这两者都需要硬件来支持原子性的操作

下面来看看mutex如何实现monitor的condition的功能的:

  1. 上锁,使用mutex.acquire(), 本质上mutex就是这个锁

  2. wait,维护一个waitingQueue,把当前线程加入这个队列, mutex.release(), 交出锁的控制权,sleep进入休眠状态, 被唤醒时重新mutex.acquire()去获得锁

  3. signal,维护一个readyQueue,将waitingQueue里面的队头取出来加入readyQueue, 由OS去唤醒readyQueue里面的线程

那么mutex怎么做到上锁、解锁这个过程的呢?

  1. 使用原子指令如test-and-set、compare-and-set来实现自旋锁来保证对关键段的访问, 无论是acquire还是release都要先处理这个过程

  2. 维护一个held的bool值, acquire()时,先去看是否held, 如果held,说明别的线程拥有这个锁, 当前线程进入blockingQueue,睡眠并等待唤醒,唤醒后或者一开始held就是false,将held设为true,表示当前线程持有了锁

  3. release()时, 直接将held设为false, 将blockingQueue中的头放到readyQueue中, OS会自行调度

pic2

总结

  • 锁, 本质上是在一段critical section代码中维护一个状态, 这个状态就代表着当前是上锁的还是没被上锁

  • 线程对锁的请求,用一个等待队列来对想要请求锁的队列进行排队,当锁释放时,拿出一个来让它获取锁

  • 如何实现condition,本质上wait是对锁的释放并把自己放入等待队列, signal就是去等待队列中唤醒一个线程, 等当前线程把锁释放后,那个线程就可以获得这个锁了

  • 回到java层面, object’s monitor可以做到上面一系列操作, 但是缺点也很明显:

    1.没办法实现非公平锁				
    2.一个锁没办法使用多个condition		  
    3.没办法中断wait操作  
    
  • 因此,还有一个java.util.concurrent.locks.Lock等着我们后面去分析