JVM线程资源同步及交互机制

java程序采用多线程的方式来支撑大量的并发请求处理,程序在多线程方式 执行的情况下,复杂程度远高于单线程串行执行程序。多线程执行程序最明显的问题是线程之间共同管理的资源的竞争及线程之间的交互。本节主要介绍JVM线程资源同步机制和线程之间的交互机制。

线程资源同步机制

首先来看一段获取id的程序

int i = 0 ; 
public int getNextInt(){
    return i++;
}

以下是上面的代码在JVM中的执行顺序:
1) JVM首先在main memory(JVM堆)中给i分配一个内存存储场所,并存储其值0;
2)线程启动后,会自动分配一片working memory区(通常是操作数栈),当程序执行到return i++ 时,JVM中并不是简单的一个步骤就可以完成的。i+动作在JVM中分为装载i、读取i、进行i+1操作、存储i及写入i五个步骤才得以完成。

  • 装载i
    线程发起一个装载i的请求给jvm线程执行引擎,引擎接受请求后会向main memory发起一个read i的指令,当read i执行完毕后,一段时间线程会将i的值从main memory 复制到 working memory中
  • 读取i
    此步负责将i的值从main memory中复制到 working memory中
  • 进行i+1操作
    此步骤由线程完成
  • 存储i
    将i+1赋值给i,然后存储到working memory中
  • 写入i
    一段时间后i会写入main memory中

这里面最关键的问题有2个:一是working memory和main memory同步i的值是需要时间的;二是i++由多个操作组成。只要多个线程在这个时间段内同时执行了操作,就会出现获取i值相同的情况。举个简单的例子:假设线程A已执行i+1操作,但是尚未执行写入i的操作,线程B就完成了i的装载,那么线程B执行完成以后,他的值就和A的一样了。
JVM把对于working memory的操作分为use、assign、load、store、lock、和unlock,对于working memory的操作指令由线程发出,对于main memory的操作分为read、write、lock和unlock;对于main memory的操作指令由线程执行引擎发出。其含义分别为:

  • use
    use由线程发起,需要完成将变量的值从working memery复制到执行引擎中
  • assign
    由线程发起,需要完成将变量值复制到线程的working memory中,例如a=i,这时线程就会发起一个assign动作
  • load
    load由线程发起,需要完成将main memory中read到的值复制到working memory中
  • store
    由线程发起,负责将变量的值从working memory 复制到 main memory中。等待main memory通过write动作写入此值
  • lock
    由线程发起,同步操作main memory,给对象上锁
  • unlock
    由线程发起,同步操作main memory,去除对象的锁

JVM保证以下操作是按顺序的:
1)同一个线程上的操作一定是顺序执行的
2)对于main memory上同一个变量的操作一定是按顺序的,也就是不可能两个请求同时读取变量值
3)对于加了锁的main memory对象上的操作,一定是顺序执行的。

为了避免资源操作的脏数据问题,JVM提供了synchronized、volatile关键字和lock/unlock机制,上述代码修改后:

int i = 0 ; 
public synchronized int getNextInt(){
    return i++;
}

当多线程执行此段代码时,线程A执行到getNextInt()方法,JVM知道此方法上有synchronized关键字,于是在执行其他动作前首先按照对象的实例ID加上一个锁lock。然后再继续执行return i++操作,而此时如果线程B并发访问getNextInt()方法,JVM观察到这个对象的实例ID上有一个lock,于是将线程B放入等待执行的队列中。只有当线程A执行完毕以后,JVM才会释放对象的实例id上的lock,重新标记为unlock,这是当下次调度到线程B时,线程B才可以得到getNextInt()方法。由于这个过程是串行的,可以保证每个线程的getNextInt都是不一样的值。

synchronized除了可以直接写在方法上,还可以写在对象上,区别仅仅在于JVM会根据这些情况lock标记是打在什么上。需要注意的是,如果使用syncronized修饰静态方法时,锁的粒度是整个类。

lock/unlock机制原理和synchronized相同,他们都可以保证某段代码执行的原子性。由于锁会阻断其他线程需要同样锁的部分执行,因此要注意避免死锁的情况,下面就是一个死锁的例子:

Object a = new Object();
Object b = new Object();
    public void call1(){
        synchronized (b) {
            synchronized (a) {
                //do something
            }
        }
    }
    public void call2(){
        synchronized (a) {
            synchronized (b) {
                //do something
            }
        }
    }

这将会导致系统挂起。
对于lock/unlock 一定要保证这两是成对出现,并且在lock完成后要unlock,否则就会有线程出现饿死的情况。
volatile的机制有所不同,他仅用于控制对象的可见性,但并不能保证在此对象上操作的原子性。就像上面的i++场景,即使把i定义为volatile也是没有用的。对于定义为volatile的变量,线程不会将其从main memory复制到work memory中,而是直接在main memory中直接操作。他的代价比synchronized和lock/unlock低,但是要小心,他不能保证操作的原子性。

线程交互机制

线程之间除了会产生资源的竞争外,还会有交互的需求。例如最典型的连接池,连接池中通常都会有get个return方法,return方法的时候需要将连接返回到缓存列表中,并将可使用的连接数+1,而在get方法的时候在判断可使用的连接数已经到0以后,需要进入一个等待状态,当有连接返回到连接池时,应该通知下get方法,不需要再等待了。如果没有这个交互机制,就只能在get方法中不断轮询判读可使用的连接数。
JVM提供了wait/notify/notifiAll方式来支持这类需求,基于Object的wait/notify/notifyAll实现的连接池,典型代码如下:

    public Connection get(){
        synchronized (this) {
            if(free>0){
                return cacheConnections.poll();
            }else{
                this.wait();
            }
        }
    }
    public void close(Connection conn){
        synchronized (this) {
            free++;
            cacheConnections.offer(conn);
            this.notifyAll();
        }
    }

Object对象的wait方法会让此线程进入等待状态,只有当其他线程调用了此Object的notify、notifyAll方法,或者wait(毫秒数)到达指定时间后,才会被激活执行。notify只是去随机找此Object处于wait状态的一个线程,而notifyAll则是唤醒所有此对象处于wait状态的线程。要注意的是object.wait还有可能被假唤醒,关于假唤醒的博文可参考:
http://www.linuxidc.com/Linux/2014-03/98715.htm

线程状态分析也有很多工具,比如java自带的jstack linux系统中使用kill -3 [pid] 有JConsole等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值