为了账号安全,请及时绑定邮箱和手机立即绑定

获取java中锁竞争的线程信息

标签:
Java

java中的synchronized的锁是有个膨胀的过程,时间越久获取的速度就越慢。所以有一些场景我们要针对线程,线程执行时间,以及线程个数有所调整。
执行时间可以通过合理控制锁的范围实现。个数就得根据业务来了。因为有可能同时出现锁竞争的概率很低,大部分时间都是一个线程在运行。
那如何去检测呢?
业务层面是不好区分的,究竟这个锁现在就几个线程在等待。java层面暂时没有提供一个api。我们把思路查看到jvmti,确实找到了对应的事件。我们下面来看看怎么利用jvmti来实现。

实现

jvmti有两种agent,一种是java的,一种是c/c++的。我们接下来使用的是c/c++的agent。

    jvmtiEnv *jvmti = NULL;
    jint ret = vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION_1_2);
    if (ret != JNI_OK) {
        fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
        return JNI_ERR;
    }

上来先是一段讨论操作,初始化jvmtienv。确定成功后才可以继续做一些操作。

    jvmtiCapabilities caps;
    std::memset(&caps, 0, sizeof(caps));
    caps.can_generate_monitor_events = 1;

这里就是把我们关注的事件的开关打开,我们是关注锁信息,所以can_generate_monitor_events需要打开。我们接下来看看我们能关注那些事件。我们可以查看文档或者对应的jvmti的头文件都可以看到。

                              /*   73 : Monitor Wait */
    jvmtiEventMonitorWait MonitorWait;
                              /*   74 : Monitor Waited */
    jvmtiEventMonitorWaited MonitorWaited;
                              /*   75 : Monitor Contended Enter */
    jvmtiEventMonitorContendedEnter MonitorContendedEnter;
                              /*   76 : Monitor Contended Entered */
    jvmtiEventMonitorContendedEntered MonitorContendedEntered;

我们可以看到和锁相关的事件有4个。通过命名,我们可以猜到功能。
MonitorWait是锁对象触发wait操作。
MonitorWaited是锁对象成功wait。
MonitorContendedEnter是进入锁操作。
MonitorContendedEntered是功能进入锁操作。
这里其实就是对应两种状态,一直是开始做了,一个是成功做到了。我们这次选择MonitorContendedEnter操作或者MonitorContendedEntered都可以,他们都能反映锁的争抢情况,MonitorContendedEnter是执行到synchronized就会触发事件,MonitorContendedEntered则是终于拿到锁资源才触发的事件,我们想看多少在抢锁资源,所以我们选择MonitorContendedEnter事件,只要大家排队等待就可以。
我们获取的信息也很简单,只要线程id和name。为什么这么少呢,因为jni操作太麻烦了,下面贴出展示代码。

void JNICALL jvmti_EventMonitorContendedEnter(jvmtiEnv *jvmti_env,
                                              JNIEnv *jni_env,
                                              jthread thread,
                                              jobject object) {

    jclass threadClass = jni_env->GetObjectClass(thread);
    jmethodID getId = jni_env->GetMethodID(threadClass, "getId", "()J");
    jlong id = (jlong) jni_env->CallObjectMethod(thread, getId);
    jmethodID getName = jni_env->GetMethodID(threadClass, "getName", "()Ljava/lang/String;");
    const char *name = jni_env->GetStringUTFChars((jstring) jni_env->CallObjectMethod(thread, getName), NULL);
    std::cout << id << name << std::endl;

}

为什么是这么一个参数呢,因为这是一个回调函数,我们需要把回调的传过去就好,这里用的是方法指针,我们只要是这个返回值,是这个参数,名字无所谓。
上面的操作纯粹的jni操作,他可以给我们线程对象,以及锁对象,只不过都需要通过JNI_ENV进行打印操作,调用java对象的方法,我这里只是输出一个id和name。

    jvmtiEventCallbacks cb;
    cb.MonitorContendedEnter = &jvmti_EventMonitorContendedEnter;
    jvmti->SetEventCallbacks(&cb, sizeof(cb));
    jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL);

这里设置我们上面的方法指针给对应的事件方法。并且打开通知模式。然后编译成动态库,这里的操作以及头文件和jni的操作一模一样。就不重复介绍了。最终生成动态库。

测试程序

  public static void main(String[] args) throws InterruptedException {

        Main main = new Main();
       new Thread(()->{
           main.hello();
       }).start();

        new Thread(()->{

            main.hello();
        }).start();
        new Thread(()->{

            main.hello();
        }).start();
 
        main.hello();

    }

    public synchronized void hello() {
        System.out.println("++++++java-"+Thread.currentThread().getName());
        System.out.println("hello");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

上面是一个简单的java多线程程序。就是锁直接加在了方法上,然后打印现在的线程名。
启动java进程带上agent,通过-agentpath:动态库地址。
然后我们会看到这样的输出

++++++java-Thread-0
hello
12Thread-1
1main
13Thread-2
++++++java-Thread-2
hello
++++++java-main
hello
++++++java-Thread-1
hello

num+name的组合就是我们agent的输出。我们可以看到先后进入等待的是线程Thread-1,main,Thread-2。执行的时候是java-Thread-2,main,Thread-1。正好是反过来的,这个也是说明synchronized确实不是公平锁。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1.6万
获赞与收藏
380

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消