`

深入理解多线程-Moniter的实现原理

阅读更多

操作系统中的管程

如果你在大学学习过操作系统,你可能还记得管程(monitors)在操作系统中是很重要的概念。同样Monitor在java同步机制中也有使用。

管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。 管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。

Java线程同步相关的Moniter

在多线程访问共享资源的时候,经常会带来可见性和原子性的安全问题。为了解决这类线程安全的问题,Java提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程能访问共享资源。这个机制的保障来源于监视锁Monitor,每个对象都拥有自己的监视锁Monitor。

先来举个例子,然后我们在上源码。我们可以把监视器理解为包含一个特殊的房间的建筑物,这个特殊房间同一时刻只能有一个客人(线程)。这个房间中包含了一些数据和代码。

Java-Monitor

如果一个顾客想要进入这个特殊的房间,他首先需要在走廊(Entry Set)排队等待。调度器将基于某个标准(比如 FIFO)来选择排队的客户进入房间。如果,因为某些原因,该客户客户暂时因为其他事情无法脱身(线程被挂起),那么他将被送到另外一间专门用来等待的房间(Wait Set),这个房间的可以可以在稍后再次进入那件特殊的房间。如上面所说,这个建筑屋中一共有三个场所。

java-monitor-associate-with-object

总之,监视器是一个用来监视这些线程进入特殊的房间的。他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。

Monitor其实是一种同步工具,也可以说是一种同步机制,它通常被描述为一个对象,主要特点是:

对象的所有方法都被“互斥”的执行。好比一个Monitor只有一个运行“许可”,任一个线程进入任何一个方法都需要获得这个“许可”,离开时把许可归还。

通常提供singal机制:允许正持有“许可”的线程暂时放弃“许可”,等待某个谓词成真(条件变量),而条件成立后,当前进程可以“通知”正在等待这个条件变量的线程,让他可以重新去获得运行许可。

监视器的实现

在Java虚拟机(HotSpot)中,Monitor是基于C++实现的,由ObjectMonitor实现的,其主要数据结构如下:

ObjectMonitor(){
    _header       = NULL;
    _count        =0;
    _waiters      =0,
    _recursions   =0;
    _object       = NULL;
    _owner        = NULL;_WaitSet= NULL;_WaitSetLock=0;_Responsible= NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;FreeNext= NULL ;_EntryList= NULL ;_SpinFreq=0;_SpinClock=0;OwnerIsThread=0;}

源码地址:objectMonitor.hpp

ObjectMonitor中有几个关键属性:

_owner:指向持有ObjectMonitor对象的线程

_WaitSet:存放处于wait状态的线程队列

_EntryList:存放处于等待锁block状态的线程队列

_recursions:锁的重入次数

_count:用来记录该线程获取锁的次数

当多个线程同时访问一段同步代码时,首先会进入_EntryList队列中,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器_count加1。即获得对象锁。

若持有monitor的线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null_count自减1,同时该线程进入_WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示

monitor

ObjectMonitor类中提供了几个方法:

获得锁

void ATTR ObjectMonitor::enter(TRAPS){Thread*constSelf= THREAD ;void* cur ;//通过CAS尝试把monitor的`_owner`字段设置为当前线程
  cur =Atomic::cmpxchg_ptr (Self,&_owner, NULL);//获取锁失败if(cur == NULL){assert(_recursions ==0,"invariant");assert(_owner      ==Self,"invariant");// CONSIDER: set or assert OwnerIsThread == 1return;}// 如果旧值和当前线程一样,说明当前线程已经持有锁,此次为重入,_recursions自增,并获得锁。if(cur ==Self){// TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++;return;}// 如果当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程if(Self->is_lock_owned ((address)cur)){assert(_recursions ==0,"internal state error");
    _recursions =1;// Commute owner from a thread-specific on-stack BasicLockObject address to// a full-fledged "Thread *".
    _owner =Self;OwnerIsThread=1;return;}// 省略部分代码。// 通过自旋执行ObjectMonitor::EnterI方法等待锁的释放for(;;){
  jt->set_suspend_equivalent();// cleared by handle_special_suspend_equivalent_condition()// or java_suspend_self()EnterI(THREAD);if(!ExitSuspendEquivalent(jt))break;//// We have acquired the contended monitor, but while we were// waiting another thread suspended us. We don't want to enter// the monitor while suspended because that would surprise the// thread that suspended us.//
      _recursions =0;
  _succ = NULL ;exit(Self);

  jt->java_suspend_self();}}

lockenter

释放锁

void ATTR ObjectMonitor::exit(TRAPS){Thread*Self= THREAD ;//如果当前线程不是Monitor的所有者if(THREAD != _owner){if(THREAD->is_lock_owned((address) _owner)){// // Transmute _owner from a BasicLock pointer to a Thread address.// We don't need to hold _mutex for this transition.// Non-null to Non-null is safe as long as all readers can// tolerate either flavor.assert(_recursions ==0,"invariant");
       _owner = THREAD ;
       _recursions =0;OwnerIsThread=1;}else{// NOTE: we need to handle unbalanced monitor enter/exit// in native code by throwing an exception.// TODO: Throw an IllegalMonitorStateException ?
       TEVENT (Exit-Throw IMSX);assert(false,"Non-balanced monitor enter/exit!");if(false){
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());}return;}}// 如果_recursions次数不为0.自减if(_recursions !=0){
     _recursions--;// this is simple recursive enter
     TEVENT (Inflatedexit- recursive);return;}//省略部分代码,根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作最终由unpark完成。

lockexit

除了enter和exit方法以外,objectMonitor.cpp中还有

void      wait(jlong millis,bool interruptable, TRAPS);void      notify(TRAPS);void      notifyAll(TRAPS);

等方法。

总结

上面介绍的就是HotSpot虚拟机中Moniter的的加锁以及解锁的原理。

通过这篇文章我们知道了sychronized加锁的时候,会调用objectMonitor的enter方法,解锁的时候会调用exit方法。事实上,只有在JDK1.6之前,synchronized的实现才会直接调用ObjectMonitor的enterexit,这种锁被称之为重量级锁。为什么说这种方式操作锁很重呢?

  • Java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统的帮忙,这就要从用户态转换到核心态,因此状态转换需要花费很多的处理器时间,对于代码简单的同步块(如被synchronized修饰的get 或set方法)状态转换消耗的时间有可能比用户代码执行的时间还要长,所以说synchronized是java语言中一个重量级的操纵。

所以,在JDK1.6中出现对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁,锁粗化(自旋锁在1.4就有 只不过默认的是关闭的,jdk1.6是默认开启的),这些操作都是为了在线程之间更高效的共享数据 ,解决竞争问题。

 

 查看带有Synchronized语句块的class文件可以看到在同步代码块的起始位置插入了moniterenter指令,在同步代码块结束的位置插入了monitorexit指令。(JVM需要保证每一个monitorenter都有一个monitorexit与之相对应,但每个monitorexit不一定都有一个monitorenter)

但是查看同步方法的class文件时,同步方法并没有通过指令monitorenter和monitorexit来完成,而被翻译成普通的方法调用和返回指令,只是在其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成

moniterenter和moniterexit指令是通过monitor对象实现的。
Synchronized的实现不仅与monitor对象有关,还与另一个东西密切相关,那就是对象头

 

 每个对象都有一个监视器锁(monitor)与之对应。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

 

执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

  • 通过这两个指令我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

Java对象头

下面重点说下java对象头。

众所周知Java中万物皆对象,那对象在内存中是怎么存储的呢?

每个对象分为三块区域:对象头、实例数据和对齐填充

  • 对象头包含两部分,第一部分是Mark Word,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,这一部分占一个字节。第二部分是Klass Pointer(类型指针),是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,这部分也占一个字节。(如果对象是数组类型的,则需要3个字节来存储对象头,因为还需要一个字节存储数组的长度)
  • 实例数据存放的是类属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐
  • 填充数据是因为虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机 中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志 位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示。
对象头存储结构

对象头存储结构

 

Synchronized通常被称为重量级锁,但是1.6之后对其进行优化,新增了轻量级锁和偏向锁,这里重点说下重量级锁,随后对Synchronized的优化简单介绍下。

从对象头的存储内容可以看出锁的状态都保存在对象头中,Synchronized也不例外,当其从轻量级锁膨胀为重量级锁时,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。

关于Synchronized的实现在java对象头里较为简单,只是改变一下标识位,并将指针指向monitor对象的起始地址,其实现的重点是monitor对象。

 

Synchronized优化

早期,Synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。庆幸的是在Java 6之后Java官方对从JVM层面对synchronized较大优化,所以现在的synchronized锁效率也优化得很不错了,Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了偏向锁、轻量级锁和自旋锁等概念,接下来我们将简单了解一下Java官方在JVM层面对Synchronized锁的优化。

偏向锁

引入偏向锁的主要原因是,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁
引入的主要目的是,为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。
偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提升程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。
但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。

其执行流程为:
获取锁

  1. 检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁标识位为01;
  2. 若为可偏向状态,则测试线程ID是否为当前线程ID,如果是,则执行步骤(5),否则执行步骤(3);
  3. 如果线程ID不为当前线程ID,则通过CAS操作竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则执行线程(4);
  4. 通过CAS竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块;
  5. 执行同步代码块

释放锁
偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。偏向锁的撤销需要等待全局安全点(这个时间点是上没有正在执行的代码)。其步骤如下:

  1. 暂停拥有偏向锁的线程,判断锁对象石是否还处于被锁定状态;
  2. 撤销偏向苏,恢复到无锁状态(01)或者轻量级锁的状态;

那么轻量级锁和偏向锁的使用场景为
轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

轻量级锁

引入轻量级锁的主要原因是,对绝大部分的锁,在整个同步周期内都不存在竞争,可能是交替获取锁然后执行。(与偏向锁的区别是,引入偏向锁是假设同一个锁都是由同一线程多次获得,而轻量级锁是假设同一个锁是由n个线程交替获得;相同点是都是假设不存在多线程竞争)
引入轻量级锁的主要目的是,在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗(多指时间消耗)
触发轻量级锁的条件是当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁,此时Mark Word的结构也变为轻量级锁的结构。如果存在多个线程同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁

其步骤如下:
获取锁

  1. 判断当前对象是否处于无锁状态(hashcode、0、01),若是,则JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word);否则执行步骤(3);
  2. JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果成功表示竞争到锁,则将锁标志位变成00(表示此对象处于轻量级锁状态),执行同步操作;如果失败则执行步骤(3);
  3. 判断当前对象的Mark Word是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态;

释放锁
轻量级锁的释放也是通过CAS操作来进行的,主要步骤如下:

  1. 取出在获取轻量级锁保存在Displaced Mark Word中的数据;
  2. 用CAS操作将取出的数据替换当前对象的Mark Word中,如果成功,则说明释放锁成功,否则执行(3);
  3. 如果CAS操作替换失败,说明有其他线程尝试获取该锁,则需要在释放锁的同时需要唤醒被挂起的线程。

自旋锁

线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。
何谓自旋锁?
所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无意义的循环即可(自旋)
自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,这样反而会带来性能上的浪费。所以说,自旋等待的时间(自旋的次数)必须要有一个限度,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起。
自旋锁在JDK 1.4.2中引入,默认关闭,但是可以使用-XX:+UseSpinning开开启,在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整;
如果通过参数-XX:preBlockSpin来调整自旋锁的自旋次数,会带来诸多不便。假如我将参数调整为10,但是很多线程都是等你刚刚退出自旋的时候就释放了锁(假如你再多自旋一两次就可以获取锁),你是不是很尴尬。于是JDK1.6引入自适应的自旋锁,让虚拟机会变得越来越聪明。

JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么做呢?线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源

轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。如果自旋之后依然没有获取到锁,也就只能升级为重量级锁了。

 

 

rel:https://www.hollischuang.com/archives/2030

rel:http://bigdatadecode.club/JavaSynchronizedTheory.html

分享到:
评论

相关推荐

    host-moniter网络设备监控软件

    Host-Moniter是一款专用于网络设备监控的软件,它提供了全面的机房管理功能,能够帮助用户实时了解网络运行状态,及时发现并处理潜在问题,确保...用户通过深入理解和熟练运用,能够更好地管理和维护自己的网络环境。

    PetCat - HuangMiao - Moniter- web (服务器监控平台).zip

    可借鉴此优质项目实现复刻,也可基于此项目来扩展开发出更多功能 #注 1. 积分资源不提供技术指导/答疑 2. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担 3. 部分字体及插图等来自网络,若是...

    GithubMonitor-GitHub敏感信息监控脚本

    GitHub敏感信息监控脚本,通过检索GitHub的搜索结果关键字来发现敏感信息,降低程序员GitHub项目上泄露敏感信息的可能性。

    File_Moniter4.34.rar_File Moniter source_filemoniter_files monit

    通过深入理解和有效使用这款工具,IT专业人士可以在日常工作中提升问题定位和处理的效率,保障系统的稳定运行。同时,了解"www.pudn.com.txt"和"File Moniter4.34"这两个文件的具体内容,将有助于用户更好地理解和...

    video_moniter1.rar

    开发者需要对QT框架有深入理解,同时熟悉视频编码和解码的基本原理,才能构建出高效、稳定且用户体验良好的视频监控回放系统。在这个过程中,"video_moniter1.rar"可能包含了实现该功能的源代码、资源文件或其他相关...

    SQL_moniter

    本文将深入探讨SQL_monitor的功能、工作原理以及如何有效利用它提升开发效率。 一、SQL_monitor的核心功能 1. SQL查询追踪:SQL_monitor能够记录并显示所有的SQL语句,包括INSERT、UPDATE、DELETE和SELECT等,这...

    PORTMON File Moniter4.34.rar源代码

    源代码的提供使得用户可以深入理解工具的工作原理,进行定制化修改或学习软件开发技巧。 标签"File Moniter4.34.rar源代码"和"PORTMON"进一步确认了这是PORTMON工具的源代码,存储在一个RAR压缩文件里。RAR是一种...

    BADY MONITER.cds

    Baby Moniter的应用简易程序,频率是40MHz

    Driver Moniter(驱动测试小软件)

    测试驱动的小软件,很好用的。

    source moniter

    source moniter代码检查 度量

    webservice10 使用moniter监视器

    ### webservice10 使用moniter监视器 #### 知识点概述 - **WebService与Axis2**: WebService是一种跨编程语言、操作系统平台的远程调用技术。Axis2是Apache的一个项目,它是一个高性能、轻量级的Web服务框架,支持...

    qira:QEMU交互式运行时分析器

    齐拉 QIRA是strace和gdb的竞争对手 有关高级用法的信息,请参见 所有QIRA代码均在MIT许可下发布 此仓库中的其他代码已根据各自的许可发布 支持的操作系统 Ubuntu 14.04 and 16.04 supported out of the box. ...

    Keil C51 moniter 仿真器PCB.rar

    《Keil C51 Monitor 仿真...通过理解其设计原理和要点,开发者可以更好地利用这一工具,提高软件调试的效率和质量。在实际操作中,还需要结合硬件知识、信号处理技术以及电磁兼容性等因素,以确保仿真器的稳定和可靠。

    moniter数据

    数据监控,可以分析空中数据帧,反应出设备运行情况

    (代码)交通视频监测程序,用以车辆跟踪、闯红灯检测及越线检测

    交通视频监测程序是一种重要的智能交通管理系统,它利用先进的计算机视觉技术、图像处理和人工智能算法,对交通...研究和理解"Moniter-master"中的代码,可以帮助我们深入掌握智能交通监控系统的实现原理和技术细节。

    free-serial-port-monitor_(串口监控调试).rar

    《免费串口监控调试工具详解》 在计算机通信领域,串口(Serial Port)作为一种古老但仍然广泛应用的接口,常用于设备之间的数据传输。...通过深入理解和熟练运用,你可以在串口调试的世界里游刃有余。

    Monitor.wait例子.rar

    在Java编程语言中,`Monitor`是一个重要的概念,它与多线程同步密切相关。`Monitor`通常是由一个或多个...通过学习和理解这些示例,开发者能够更好地掌握这些同步原语的使用,从而编写出更健壮、高效的多线程Java程序。

    kafka 监控工具

    总的来说,Kafka应用监控是一项复杂而重要的任务,需要结合多种工具和技术,对Kafka集群的多个层面进行深入监控,从而确保系统的健壮性和高可用性。理解并掌握这些监控手段和指标,对于维护一个高效、可靠的Kafka...

    Ceph 进程间通信.pptx

    通过对Ceph进程间通信机制的研究,我们不仅可以深入了解Ceph内部的工作原理,还可以学习到如何在分布式系统中高效地进行进程间通信。这对于构建高可用、高性能的分布式存储系统具有重要的参考价值。

    Windows进程监视器(Procmon-微软官网版本)

    Procmon是由Sysinternals Suite的一部分,它提供了深入的系统级洞察力,帮助用户诊断和解决各种系统问题。** Procmon的工作原理: ------------------ Procmon能够监控以下关键事件: 1. **进程创建与退出**: ...

Global site tag (gtag.js) - Google Analytics