乐趣区

大牛聊Java并发编程原理之-线程的互斥与协作机制

可能在 synchronized 关键字的实现原理中,你曾经晓得了它的底层是应用 Monitor 的相干指令来实现的,然而还不分明 Monitor 的具体细节。本文将让你彻底 Monitor 的底层实现原理。

管程

一个管程能够被认为是一个带有非凡房间的修建,这个非凡房间只能被一个线程占用。这个房间蕴含很多数据和代码。

如果一个线程要占用非凡房间 (也就是红色区域),那么首先它必须在 Hallway 中期待。调度器基于某些规定(例如先进先出) 从 Hallway 中取一个线程。如果线程在 Hallway 因为某些起因被挂起,它将会被送往期待房间(也就是蓝色区域),在一段时间后被调度到非凡房间中。

简而言之,监视器是一种监督现场拜访非凡房间的设施。他可能使有且仅有一个线程拜访的受爱护的代码和数据。

Monitor

在 Java 虚拟机中,每一个对象和类都与一个监视器相关联。为了实现监视器的互斥性能,锁 (有时候也称为互斥体) 与每一个对象和类关联。在操作系统书中,这叫做信号量,互斥锁也被称为二元信号量。

如果一个线程领有某些数据上的锁,其余线程想要取得锁只能等到这个线程开释锁。如果咱们在进行多线程编程时总是须要编写一个信号量,那就不太不便了。侥幸的是,咱们不须要这样做,因为 JVM 会主动为咱们做这件事。

为了申明一个同步区域(这里意味着数据不可能被超过一个线程拜访),Java 提供了 synchronized 块和 synchronized 办法。一旦代码被 synchronized 关键字绑定,它就是一个监视器区域。它的锁将会在前面被 JVM 实现。

Monitor 是 Java 中用以实现线程之间的互斥与合作的次要伎俩,它能够看成是对象或者 Class 的锁。每一个对象都有,也仅有一个 monitor。上面这个图,形容了线程和 Monitor 之间关系,以及线程的状态转换图:

进入区(Entrt Set):示意线程通过 synchronized 要求获取对象的锁,但并未失去。

拥有者(The Owner):示意线程胜利竞争到对象锁。

期待区(Wait Set):示意线程通过对象的 wait 办法,开释对象的锁,并在期待区期待被唤醒。

线程状态

  • NEW,未启动的。不会呈现在 Dump 中。
  • RUNNABLE,在虚拟机内执行的。
  • BLOCKED,期待取得监视器锁。
  • WATING,无限期期待另一个线程执行特定操作。
  • TIMED_WATING,有时限的期待另一个线程的特定操作。
  • TERMINATED,已退出的。

举个例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
 * Hello world!
 *
 */
public class App {public static void main(String[] args) throws InterruptedException {MyTask task = new MyTask();
       Thread t1 = new Thread(task);
       t1.setName("t1");
       Thread t2 = new Thread(task);
         t2.setName("t2");
        t1.start();
         t2.start();}

}

class MyTask implements Runnable {

   private Integer mutex;

   public MyTask() {mutex = 1;}

   @Override
   public void run() {synchronized (mutex) {while(true) {System.out.println(Thread.currentThread().getName());
           try {TimeUnit.SECONDS.sleep(5);
           } catch (InterruptedException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();}
          }
        }
   }

}

线程状态:

"t2" prio=10 tid=0x00007f7b2013a800 nid=0x67fb waiting for monitor entry [0x00007f7b17087000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:35)
  - waiting to lock <0x00000007d6b6ddb8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f7b20139000 nid=0x67fa waiting on condition [0x00007f7b17188000]
 java.lang.Thread.State: TIMED_WAITING (sleeping)
  at java.lang.Thread.sleep(Native Method)

t1 没有抢到锁,所以显示BLOCKED。t2 抢到了锁,然而处于睡眠中,所以显示TIMED_WAITING,无限期待某个条件来唤醒。

把睡眠的代码去掉,线程状态变成了:

"t2" prio=10 tid=0x00007fa0a8102800 nid=0x6a15 waiting for monitor entry [0x00007fa09e37a000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:35)
  - waiting to lock <0x0000000784206650> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007fa0a8101000 nid=0x6a14 runnable [0x00007fa09e47b000]
 java.lang.Thread.State: RUNNABLE
  at java.io.FileOutputStream.writeBytes(Native Method)

t1 显示 RUNNABLE,阐明正在运行,这里须要额定阐明一下,如果这个线程正在查询数据库,然而数据库产生死锁,尽管线程显示在运行,实际上并没有工作,对于IO 型的线程别只用线程状态来判断工作是否失常。
MyTask 的代码小改一下,线程拿到锁之后执行 wait,开释锁,进入期待区。

public void run() {synchronized (mutex) {if(mutex == 1) {
             try {mutex.wait();
             } catch (InterruptedException e) {e.printStackTrace();
             }
         }
      }
  }

线程状态如下:

"t2" prio=10 tid=0x00007fc5a8112800 nid=0x5a58 in Object.wait() [0x00007fc59b58c000]
 java.lang.Thread.State: WAITING (on object monitor)
  at java.lang.Object.wait(Native Method)

"t1" prio=10 tid=0x00007fc5a8111000 nid=0x5a57 in Object.wait() [0x00007fc59b68d000]
 java.lang.Thread.State: WAITING (on object monitor)
  at java.lang.Object.wait(Native Method)

两个线程都显示 WAITING,这次是无限期的,须要从新取得锁,所以前面跟了on object monitor
再来个死锁的例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
 * Hello world!
 *
 */
public class App {public static void main(String[] args) throws InterruptedException {MyTask task1 = new MyTask(true);
        MyTask task2 = new MyTask(false);
        Thread t1 = new Thread(task1);
        t1.setName("t1");
        Thread t2 = new Thread(task2);
        t2.setName("t2");
        t1.start();
        t2.start();}

}

class MyTask implements Runnable {

    private boolean flag;

    public MyTask(boolean flag) {this.flag = flag;}

    @Override
    public void run() {if(flag) {synchronized (Mutex.mutex1) {
                try {TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();}
                synchronized (Mutex.mutex2) {System.out.println("ok");
                }
            }
        } else {synchronized (Mutex.mutex2) {
                try {TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();}
                synchronized (Mutex.mutex1) {System.out.println("ok");
                }
            }
        }
    }

}

class Mutex {
   public static Integer mutex1 = 1;
   public static Integer mutex2 = 2;
}  

线程状态:

"t2" prio=10 tid=0x00007f5f9c122800 nid=0x3874 waiting for monitor entry [0x00007f5f67efd000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:55)
  - waiting to lock <0x00000007d6c45bd8> (a java.lang.Integer)
  - locked <0x00000007d6c45be8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f5f9c121000 nid=0x3873 waiting for monitor entry [0x00007f5f67ffe000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:43)
  - waiting to lock <0x00000007d6c45be8> (a java.lang.Integer)
  - locked <0x00000007d6c45bd8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x00007f5f780062c8 (object 0x00000007d6c45bd8, a java.lang.Integer),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007f5f78004ed8 (object 0x00000007d6c45be8, a java.lang.Integer),
which is held by "t2"

这个有点像哲学家就餐问题,每个线程都持有对方须要的锁,那就运行不上来了。

最初

私信回复 材料 支付一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 外围知识点总结!

这些材料的内容都是面试时面试官必问的知识点,篇章包含了很多知识点,其中包含了有基础知识、Java 汇合、JVM、多线程并发、spring 原理、微服务、Netty 与 RPC、Kafka、日记、设计模式、Java 算法、数据库、Zookeeper、分布式缓存、数据结构等等。

作者:monitor

退出移动版