synchronized 与 object's Monitor
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 (<condition does not hold>)
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的执行序列图吧:
那么到底到底,monitor怎么实现的呢?
还是wiki了一下https://en.wikipedia.org/wiki/Monitor_%28synchronization%29
可以通过我们学过的操作系统中的mutex和semaphore来实现,前者保证同步,后者保证互斥,这两者都需要硬件来支持原子性的操作
下面来看看mutex如何实现monitor的condition的功能的:
-
上锁,使用mutex.acquire(), 本质上mutex就是这个锁
-
wait,维护一个waitingQueue,把当前线程加入这个队列, mutex.release(), 交出锁的控制权,sleep进入休眠状态, 被唤醒时重新mutex.acquire()去获得锁
-
signal,维护一个readyQueue,将waitingQueue里面的队头取出来加入readyQueue, 由OS去唤醒readyQueue里面的线程
那么mutex怎么做到上锁、解锁这个过程的呢?
-
使用原子指令如test-and-set、compare-and-set来实现自旋锁来保证对关键段的访问, 无论是acquire还是release都要先处理这个过程
-
维护一个held的bool值, acquire()时,先去看是否held, 如果held,说明别的线程拥有这个锁, 当前线程进入blockingQueue,睡眠并等待唤醒,唤醒后或者一开始held就是false,将held设为true,表示当前线程持有了锁
-
release()时, 直接将held设为false, 将blockingQueue中的头放到readyQueue中, OS会自行调度
总结
-
锁, 本质上是在一段critical section代码中维护一个状态, 这个状态就代表着当前是上锁的还是没被上锁
-
线程对锁的请求,用一个等待队列来对想要请求锁的队列进行排队,当锁释放时,拿出一个来让它获取锁
-
如何实现condition,本质上wait是对锁的释放并把自己放入等待队列, signal就是去等待队列中唤醒一个线程, 等当前线程把锁释放后,那个线程就可以获得这个锁了
-
回到java层面, object’s monitor可以做到上面一系列操作, 但是缺点也很明显:
1.没办法实现非公平锁 2.一个锁没办法使用多个condition 3.没办法中断wait操作
-
因此,还有一个java.util.concurrent.locks.Lock等着我们后面去分析