Java同步机制,wait(),notify(),park(),unpark()及中断机制

同步机制 Synchronization

 画了一个简易的Java内存模型

在JVM中,主内存是公共区域,每个线程有自己独立的工作内存,所以就会有可见性的问题(两个线程同时从主内存拿到变量n到自己的工作内存,一个线程对其更改完放回主内存,是否会通知到另一个线程)

synchronized 关键字 是同步关键字,也就是所说的加锁

Java中每个对象都关联一个监视器monitor

线程对每个对象加锁 意味着 线程获取到该对象的monitor监视器锁,一旦一个线程占有锁,其他线程只能加入到同步队列,等待锁释放 就是大名鼎鼎的Synchronized

当锁释放时,同步队列里的线程会蜂拥而至的抢锁,抢不到继续回同步队列。 (这里的同步队列不同于上图的等待队列)

这里捋清楚,一共有两个队列——同步队列,等待队列

同步队列会与monitor关联,主要提供给 抢对象锁抢不到的的线程一个地方坐牢。

等待队列用于线程执行wait(), 线程会进入该队列等待其他线程notify()唤醒出来

thread执行join()方法发生了什么?

join()方法会将当前线程加入到等待队列,把资源让给其他线程,等待被唤醒

notify() 隐藏JVM底层编码,

在线程 t执行join()后,当前线程被打断 加入到等待队列, t执行结束后,由 jvm 自动调用 t.notifyAll() 唤醒之前被打断的线程

park()与unpark()方法

看到JUC相关底层代码经常出现park() unpark()

LockSupport.park()  将当前线程挂起, 该线程状态会从 RUNNABLE 变成 WAITING

LockSupport.unpark(Thread t)  将线程t唤醒 从 WAITING 回到 RUNNABLE

机制其实与wait() notify()相似,只不过wait() notify()经常伴随着抢锁 释放锁, 而park() unpark()只是单纯挂起某个线程,唤醒某个线程

1. park 和 unpark 无需事先获取锁,或者说跟锁压根无关。

2. 没有什么等待队列一说,unpark 会精准唤醒某一个确定的线程。

3. park 和 unpark 没有顺序要求,可以先调用 unpark

park()底层原理

线程有一个计数器,初始值为0

调用 park 就是: 如果这个值为0,就将线程挂起,状态改为 WAITING。如果这个值为1,则将这个值改为0,其余的什么都不做。

调用 unpark 就是: 将这个值改为1

重量级锁?

然后由于阻塞和唤醒依赖于底层的操作系统实现,系统调用存在用户态与内核态之间的切换,所以有较高的开销,因此称之为重量级锁。

可重入锁

意味着一个线程获取到monitor锁,该线程可以再次获取这把锁,重入+1。每次解锁操作会反转一次加锁产生的效果

Synchronized作用于代码块,

  Synchronized(对象)  锁住一块代码,直到获取锁的线程执行完这块代码,或者抛异常结束,或者wait() 才能有下一个线程进入到这块代码

Synchronized作用于方法,

  默认锁住的是拥有该方法的对象,如果该方法是static静态方法则锁住的是拥有该方法class类。

  同样原理,如果上的对象锁,那该对象里所有加了synchronized的对象方法全部被锁住,不能有其他线程进入

  同理,如果上了类锁,则该类中所有的加了synchronized的static类方法全部被锁住,直到释放锁

Java wait(), notify(), notifyAll(), interrupt机制

如图 Object类的重要方法  wait(), notify(), notiryAll()

而 interrupt()等方法属于Thread类,这就涉及到了 线程与对象之间的交互

如最上边一图,Object对象维护了一个等待集合,专门负责将执行了wait()方法的线程加入进来(这种思想有点类似于AQS)

原理就是:

线程t 必须持有该对象的monitor锁才能进行等待,唤醒等操作。

线程t 执行了wait()等待,线程t会释放锁,然后将线程t 加入到该对象等待集合,等待有缘人来唤醒 或者中断

线程d 持有了对象锁后。执行了notyfy()方法 会随机唤醒等待集合中的一个线程,或者将等待集合中的所有线程全部唤醒,然后一起抢锁

被唤醒的线程t 会从等待集合中移除,然后阻塞等待着获取对象锁,获取到对象锁之后才能继续执行之前的代码

中途如果有中断的产生,几种情况 (以下都是copy来的)

 例子1  正常的wait() notify()交互

public class WaitNotify {

    public static void main(String[] args) {

        Object object = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {

                synchronized (object) {
                    System.out.println("线程1 获取到监视器锁");
                    try {
                        object.wait();
                        System.out.println("线程1 恢复啦。我为什么这么久才恢复,因为notify方法虽然早就发生了,可是我还要获取锁才能继续执行。");
                    } catch (InterruptedException e) {
                        System.out.println("线程1 wait方法抛出了InterruptedException异常");
                    }
                }
            }
        }, "线程1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("线程2 拿到了监视器锁。为什么呢,因为线程1 在 wait 方法的时候会自动释放锁");
                    System.out.println("线程2 执行 notify 操作");
                    object.notify();
                    System.out.println("线程2 执行完了 notify,先休息3秒再说。");
                    try {
                        Thread.sleep(3000);
                        System.out.println("线程2 休息完啦。注意了,调sleep方法和wait方法不一样,不会释放监视器锁");
                    } catch (InterruptedException e) {

                    }
                    System.out.println("线程2 休息够了,结束操作");
                }
            }
        }, "线程2").start();
    }
}

output:
线程1 获取到监视器锁
线程2 拿到了监视器锁。为什么呢,因为线程1 在 wait 方法的时候会自动释放锁
线程2 执行 notify 操作
线程2 执行完了 notify,先休息3秒再说。
线程2 休息完啦。注意了,调sleep方法和wait方法不一样,不会释放监视器锁
线程2 休息够了,结束操作
线程1 恢复啦。我为什么这么久才恢复,因为notify方法虽然早就发生了,可是我还要获取锁才能继续执行。


例子2  例子1基础上 加入了中断

public class WaitNotify {

    public static void main(String[] args) {

        Object object = new Object();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {

                synchronized (object) {
                    System.out.println("线程1 获取到监视器锁");
                    try {
                        object.wait();
                        System.out.println("线程1 恢复啦。我为什么这么久才恢复,因为notify方法虽然早就发生了,可是我还要获取锁才能继续执行。");
                    } catch (InterruptedException e) {
                        System.out.println("线程1 wait方法抛出了InterruptedException异常,即使是异常,我也是要获取到监视器锁了才会抛出");
                    }
                }
            }
        }, "线程1");
        thread1.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("线程2 拿到了监视器锁。为什么呢,因为线程1 在 wait 方法的时候会自动释放锁");
                    System.out.println("线程2 设置线程1 中断");
                    thread1.interrupt();
                    System.out.println("线程2 执行完了 中断,先休息3秒再说。");
                    try {
                        Thread.sleep(3000);
                        System.out.println("线程2 休息完啦。注意了,调sleep方法和wait方法不一样,不会释放监视器锁");
                    } catch (InterruptedException e) {

                    }
                    System.out.println("线程2 休息够了,结束操作");
                }
            }
        }, "线程2").start();
    }
}
output:
线程1 获取到监视器锁
线程2 拿到了监视器锁。为什么呢,因为线程1 在 wait 方法的时候会自动释放锁
线程2 设置线程1 中断
线程2 执行完了 中断,先休息3秒再说。
线程2 休息完啦。注意了,调sleep方法和wait方法不一样,不会释放监视器锁
线程2 休息够了,结束操作
线程1 wait方法抛出了InterruptedException异常,即使是异常,我也是要获取到监视器锁了才会抛出

例子3   notify() 和interrupt()交互
public class WaitNotify {

    volatile int a = 0;

    public static void main(String[] args) {

        Object object = new Object();

        WaitNotify waitNotify = new WaitNotify();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {

                synchronized (object) {
                    System.out.println("线程1 获取到监视器锁");
                    try {
                        object.wait();
                        System.out.println("线程1 正常恢复啦。");
                    } catch (InterruptedException e) {
                        System.out.println("线程1 wait方法抛出了InterruptedException异常");
                    }
                }
            }
        }, "线程1");
        thread1.start();

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {

                synchronized (object) {
                    System.out.println("线程2 获取到监视器锁");
                    try {
                        object.wait();
                        System.out.println("线程2 正常恢复啦。");
                    } catch (InterruptedException e) {
                        System.out.println("线程2 wait方法抛出了InterruptedException异常");
                    }
                }
            }
        }, "线程2");
        thread2.start();

         // 这里让 thread1 和 thread2 先起来,然后再起后面的 thread3
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("线程3 拿到了监视器锁。");
                    System.out.println("线程3 设置线程1中断");
                    thread1.interrupt(); // 1
                    waitNotify.a = 1; // 这行是为了禁止上下的两行中断和notify代码重排序
                    System.out.println("线程3 调用notify");
                    object.notify(); //2
                    System.out.println("线程3 调用完notify后,休息一会");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                    }
                    System.out.println("线程3 休息够了,结束同步代码块");
                }
            }
        }, "线程3").start();
    }
}

// 最常见的output:
线程1 获取到监视器锁
线程2 获取到监视器锁
线程3 拿到了监视器锁。
线程3 设置线程1中断
线程3 调用notify
线程3 调用完notify后,休息一会
线程3 休息够了,结束同步代码块
线程2 正常恢复啦。
线程1 wait方法抛出了InterruptedException异常

最后提一下
例3 的输出结果不是绝对的,根据中断和唤醒的先后顺序,如果先中断后唤醒 则直接抛异常。 如果先唤醒后中断 则正常返回。
原文地址:https://www.cnblogs.com/ttaall/p/13879002.html