Java多线程同步中同步代码块、wait、notify与notifyAll的真正含义与工作原理

来源:互联网 发布:网易房产数据 北京 编辑:程序博客网 时间:2024/06/07 13:44

今天在和导师讨论Java多线程编程的同步问题时,发现自己对同步代码块、wait()方法、notify()方法和notifyAll()方法的理解不太清晰,于是在网上查阅资料,可是结果众说纷纭,又在导师的启发和指导下结合编程验证后得出如下结论。

  1. Java中的每一个对象实例都有一个锁标记和锁池,锁标记默认允许进入。当一个线程尝试进入以该对象为锁的同步代码块时,JVM会执行获取锁的操作,该操作首先查看锁标记是否为允许进入:

    • 如果允许进入,则JVM将该标记改为不允许进入后,允许该线程进入该同步代码块执行。
    • 如果不允许进入,则JVM不改动该标记(保持为不允许进入状态),同时保存该线程的上下文环境后将其休眠,并加入锁池中。
  2. 当允许进入该同步代码块中执行的线程退出该同步代码块时,JVM执行释放锁的操作,该操作会检查锁池中是否存在休眠的线程:

    • 如果锁池不为空,则JVM选取一个锁池中的线程将其移出,并为其恢复上下文环境,唤醒并让其参与线程调度。(因为休眠前刚好进入要进入同步代码块,所以当其获取到CPU执行权限后就开始执行同步代码块中的代码了)
    • 如果锁池为空,则JVM将该同步代码块的锁标记置为允许进入即可。
  3. 当在同步代码块中执行wait()操作时,JVM的执行流程如下:

    1. 对该对象执行释放锁的操作(见2)。
    2. 保存本线程的上下文环境,将本线程加入该对象的等待池中,然后休眠不再参与CPU调度。
  4. 当同步代码块中的代码执行notify方法时,JVM会从目标对象的等待池中选出一个线程并将其移到锁池中;而如果执行的是notifyAll方法,则将等待池中的所有线程一并加入锁池中。需要注意的是,无论notify方法还是notifyAll方法,都只是在等待池与锁池之间进行移动操作,并不会唤醒任何线程。只有当前线程退出同步代码块后,JVM才会从锁池中选出一个线程让其继续执行。(由于进入等待池的线程在休眠前保存了上下文环境,所以其在继续执行后是从wait()之中恢复并执行的,保证了执行结果的一致性)

    下面通过一段代码对以上所述进行验证:

    import java.util.concurrent.TimeUnit;public class CMTTest {public static void main(String[] args) {        Object co = new Object();        System.out.println(co);        for (int i = 0; i < 5; i++) {            MyThread t = new MyThread("Thread" + i, co);            t.start();        }        try {            TimeUnit.SECONDS.sleep(20);            synchronized (co) {                System.out.println("Main Thread entered syn block...begin to invoke notifyAll()");                co.notifyAll();                System.out.println("Main Thread return from notifyAll()...begin to sleep");                TimeUnit.SECONDS.sleep(2);                System.out.println("Main Thread quit from syn block.");                     }        } catch (InterruptedException e) {            e.printStackTrace();        }    }static class MyThread extends Thread {    private String name;    private Object co;    public MyThread(String name, Object o) {            this.name = name;            this.co = o;    }@Override    public void run() {        try {            System.out.println(name + " trying to enter syn block.");            synchronized (co) {                System.out.println(name + " entered the syn block.Now sleep 2s...");                TimeUnit.SECONDS.sleep(2);                System.out.println(name + " returm from sleep, begin to invoke wait()...");                co.wait();                System.out.println(name + " returm from wait()");                int nLoopTime = 3;                while(nLoopTime != 0) {                    System.out.println(name + " is in loop for " + nLoopTime + " time");                    TimeUnit.SECONDS.sleep(1);                    nLoopTime--;                }            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}}

    这段代码的执行结果如下:

    java.lang.Object@70dea4eThread0 trying to enter syn block.Thread1 trying to enter syn block.Thread0 entered the syn block.Now sleep 2s...Thread2 trying to enter syn block.Thread3 trying to enter syn block.Thread4 trying to enter syn block.Thread0 returm from sleep, begin to invoke wait()...Thread4 entered the syn block.Now sleep 2s...Thread4 returm from sleep, begin to invoke wait()...Thread3 entered the syn block.Now sleep 2s...Thread3 returm from sleep, begin to invoke wait()...Thread2 entered the syn block.Now sleep 2s...Thread2 returm from sleep, begin to invoke wait()...Thread1 entered the syn block.Now sleep 2s...Thread1 returm from sleep, begin to invoke wait()...Main Thread entered syn block...begin to invoke notifyAll()Main Thread return from notifyAll()...begin to sleepMain Thread quit from syn block.Thread1 returm from wait()Thread1 is in loop for 3 timeThread1 is in loop for 2 timeThread1 is in loop for 1 timeThread2 returm from wait()Thread2 is in loop for 3 timeThread2 is in loop for 2 timeThread2 is in loop for 1 timeThread3 returm from wait()Thread3 is in loop for 3 timeThread3 is in loop for 2 timeThread3 is in loop for 1 timeThread4 returm from wait()Thread4 is in loop for 3 timeThread4 is in loop for 2 timeThread4 is in loop for 1 timeThread0 returm from wait()Thread0 is in loop for 3 timeThread0 is in loop for 2 timeThread0 is in loop for 1 time

    从中可以看出以下几点:

    1. 同步代码块在同一时间只允许一个线程进入
    2. 在同步代码块中调用wait()方法会执行释放锁的操作
    3. 调用notifyAll方法后,处于wait()状态的线程们并没有被立刻唤醒,而是等当前线程退出同步代码块后才顺序执行
    4. 如果该notifyAll方法为notify方法,则最终只有一个线程能从从wait方法中返回。
    5. 最后补充一点,wait、notify和notifyAll方法是任何Object及其派生类对象都有的方法,其调用范围仅限于同步方法和同步代码块中,在外部直接调用会抛java.lang.IllegalMonitorStateException运行时异常。

    所以,之前将Java的多线程同步与Windows的多线程同步直接进行类比,特别是按照语义直接想当然地理解wait和notify、notifyAll()等方法得出的结论是错误的。纸上得来终觉浅,绝知此事要躬行啊!

阅读全文
0 0
原创粉丝点击