文档章节

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

o
 osc_9gohs268
发布于 07/13 08:40
字数 1574
阅读 16
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

可能在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

o
粉丝 0
博文 77
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Netty那点事(三)Channel与Pipeline

Channel是理解和使用Netty的核心。Channel的涉及内容较多,这里我使用由浅入深的介绍方法。在这篇文章中,我们主要介绍Channel部分中Pipeline实现机制。为了避免枯燥,借用一下《盗梦空间》的...

黄亿华
2013/11/24
2W
22
用vertx实现高吞吐量的站点计数器

工具:vertx,redis,mongodb,log4j 源代码地址:https://github.com/jianglibo/visitrank 先看架构图: 如果你不熟悉vertx,请先google一下。我这里将vertx当作一个容器,上面所有的圆圈要...

jianglibo
2014/04/03
4.4K
3
Flappy Bird(安卓版)逆向分析(一)

更改每过一关的增长分数 反编译的步骤就不介绍了,我们直接来看反编译得到的文件夹 方法1:在smali目录下,我们看到org/andengine/,可以知晓游戏是由andengine引擎开发的。打开/res/raw/at...

enimey
2014/03/04
6.2K
18
SQLServer实现split分割字符串到列

网上已有人实现sqlserver的split函数可将字符串分割成行,但是我们习惯了split返回数组或者列表,因此这里对其做一些改动,最终实现也许不尽如意,但是也能解决一些问题。 先贴上某大牛写的s...

cwalet
2014/05/21
9.7K
0
Swift百万线程攻破单例(Singleton)模式

一、不安全的单例实现 在上一篇文章我们给出了单例的设计模式,直接给出了线程安全的实现方法。单例的实现有多种方法,如下面: class SwiftSingleton { } 这段代码的实现,在shared中进行条...

一叶博客
2014/06/20
3.6K
16

没有更多内容

加载失败,请刷新页面

加载更多

旋转子段 (思维stl)

题目: 大概意思就是给你一个序列,你可以选择一段区间使它左右翻折一遍,然后呢,从1到n找一遍,看a[i]==i的数最多是多少。 其实刚才我已经把暴力思路说出来了,枚举每一个区间长度,枚举每...

osc_npw5uz1o
7分钟前
0
0
回忆录

前言? 果然退役的蒟蒻不仅没有留下有价值的学习资料,甚至连能看的颓废资料都没有。 其实这一年时间里一直想写一篇像样的回忆录。 想把高三也写进去?现在高三结束了。没时间写?现在有了。...

osc_z9ptnny9
9分钟前
0
0
mysql启动失败,unit not found

1 mysql启动 Failed to start mysqld.service: Unit not found. 2 查询/etc/init.d/下是否存在mysqld ll /etc/init.d/ | grep mysqld 发现该目录下并没有mysqld的文件,若存在,请备份一下 ...

osc_um3gbrdm
10分钟前
5
0
域名解析到底应该肿么破——详解域名解析类型

原文地址:https://www.wjcms.net/archives/%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E5%88%B0%E5%BA%95%E5%BA%94%E8%AF%A5%E8%82%BF%E4%B9%88%E7%A0%B4%E8%AF%A6%E8%A7%A3%E5%9F%9F%E5%90%8D%......

神兵小将
10分钟前
0
0
Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在...

osc_3r4js8qy
12分钟前
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部