zoukankan      html  css  js  c++  java
  • 大牛聊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

    file

  • 相关阅读:
    android 的通知管理
    java 反射机制
    java基础知识梳理
    spring 知识梳理
    Orange's_1_win7下搭建环境
    编写安全代码:死循环
    我的kindle书单
    [更新Github地址]python学习,自己写了个简单聊天工具mychat
    给VIM和Terminal配色:Solarized
    Hive学习之路 (八)Hive中文乱码
  • 原文地址:https://www.cnblogs.com/MonsterJ/p/13289565.html
Copyright © 2011-2022 走看看