JVM的线程内存模式与锁分析

引言:在Java语言中,线程的引入对于多核时代而言是一个巨大的福音,与之伴生的是同步与锁的使用让Java线程的管理、调试变得异常复杂,为了帮助大家更深入的理解线程的使用,我们首先来深入了解线程的内存操作模型,然后一切都会变得云开雾散。

1. 进程与线程

   最早的进程概念来自于操作系统的课本之中,进程是一个程序代码执行的载体,包括:寻址空间地址,程序计数器、栈空间,执行会依赖于CPU的计算时间片。线程与进程既存在相关性,也存在很大的不同,应用场景和解决的问题也存在一定的差异。下面我们来详细描述一下这些内容。

   进程和线程的相关性:

  •   线程依赖于进程; 一个进程之中可以创建若干个线程,进程是这些线程存在的物质前提基础,线程继承了进程的资源内容,线程有自己独立的栈、寄存器和程序计数器,这些是线程能够独立执行某些逻辑和代码的核心点,线程独立的栈就是我们后面提到了存在并发同步问题的问题根源,线程中的栈与进程的内存空间之间的数据同步以及交互则造成了这种问题。
  •  二者都可以进行程序逻辑的执行,从外部来看,是独立的执行载体和调度单元
  •  两者的状态转换过程基本一致,创建、执行、阻塞、唤醒、销毁等等。

     进程与线程的差异性:

  •   进程有独立的寻址空间,而线程则没有,共享于进程的资源。
  •   线程是一个轻量级的执行载体,允许在一个进程内部实现更为细粒度的并发执行,创建和销毁的成本更低,故而效率更高。
  •  

      多进程与多线程的架构对比:

  •   多进程应用开销大于多线程应用,但稳定性大于多线程应用,原因在于单个进程的崩溃并不影响整个应用的正常运转;在多线程中,单个线程的崩溃将直接导致整个进程的奔溃和不可用。
  •   对于多核CPU的服务器而言,线程可以发挥更高效的计算能力和处理能力

2. 线程的内存结构

   线程的基本概念包括:  

  •    每一个线程有一个工作内存和主存独立
  •   工作内存存放主存中变量的值的拷贝

    由于线程在执行过程中,存在独立的栈和工作内存,故在线程的独立区域和进程的共享区域之间存在数据交互,由于数据交互的需求存在,故在进程主存与多个线程之间的数据之间就存在了复制、交换先后顺序以及顺序不确定之后的冲突问题。

    当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作

     当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作。

    单个的read/store操作是原子性的,但是放到一起则不能保障原子性。换句话来讲,就是单个线程进行将数据从某个线程回写到进程主存,但是无法保障其它线程同步可以看到,且其中存在一定的概率的顺序不确定,比如先看到旧值,然后写入新值;或者写入新值之后,读取新值等多种可能性。

   基于以上的分析,大家可以发现线程同步需求的根源在于线程存在自己独立的工作空间,但是线程独立的工作空间需要与进程的工作空间进行数据交互和同步,不同的线程之间依赖于进程层面的主存空间进行数据交互;不幸的是工作空间的数据交互在单个线程看起来是顺序和确定的,但是对于多个线程而言则是乱序的,且load/read/store单个操作是原子性的;完成一个同步操作需要2个步骤,这2个步骤不是原子性的操作。详细过程可以参照下图

   

3.  Volatile

   volatile可以保证单个变量的写入或者同步是线程安全的, 这里需要简单描述一下,何为线程安全? 线程安全就是在一个进程之中的多个线程之间,在单个线程修改了某个变量之后,可以保障其可以正确的被其余线程看到。  volatile要解决的问题也是保障变量的线程安全。

   volatile只适合于单个的简单数据类型变量,指示CPU在读取/写入线程内的变量之时,直接从进程的内存中的变量来进行;直接调过线程的变量以及线程变量与进程变量之间的同步操作,从而保证了各个线程在执行过程中的正确性。

    代码示例:

public class VolatileStopThread extends Thread{
private volatile boolean stop = false;
public void stopMe(){
stop=true;
}

public void run(){
  int i=0;
  while(!stop){
       i++;
    }
  System.out.println("Stop thread");
}

public static void main(String args[]) throws InterruptedException{
VolatileStopThread t=new VolatileStopThread();
t.start();
Thread.sleep(1000);
t.stopMe();
Thread.sleep(1000);
}
上述的代码可以正常执行,但是在移除volatile的标识符之后,则程序会陷入死循环,问题就在于volatile带来的stop变量值本身的线程安全性问题。

  volatile的使用可以解决简单变量的同步问题,但是对于其它的场景则无能为力,这个也是其局限性。

4.  i++的线程安全性深入分析

    我们尝试从JVM执行的角度来看看一个i++自增操作将完成何种操作:

0: aload_0
1: getfield #17;
4: dup
5: getfield #26; //获取i的值,并将其压入栈顶
8: iconst_1 //将int型1压入栈顶
9: iadd //将栈顶两int型数值相加,并将结果压入栈顶
10: putfield #26; //将栈顶的结果赋值给i
13: aload_0
14: getfield #19;
从上述的JVM执行中,我们可以看到,一个简单的i++操作,需要经历多个步骤才可以完成,而在这些步骤之间是无法保证其原子性的,这个就是其i++的线程不安全的根源。

5.  AtomicInteger之类的线程安全类型

    在JDK 5之后出现了大量的Atomic开头的数据类型,其可以很好的解决了数据类型在多个线程之间的安全性问题,即可以替代volatile的使用。那他们可以保证线程安全性的原因是什么呢?

    打开源代码之后,可以发现其底层使用了非Java语言写的操作:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj,
jlong offset, jint e, jint x))
   UnsafeWrapper("Unsafe_CompareAndSwapInt");
   oop p = JNIHandles::resolve(obj);
   jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
   return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
  其底层则是调用了CPU的指令,其CPU的指令本身是原子性的,这个是其能够保证操作原子性的根源。 这里的方法是Atomic:cmpxchg方法保证其原子性。

6.   synchronized以及各类锁

   synchronized为代表的各类锁都是用来保证对于进程内的各个线程在执行某段代码之时,将这段代码标识为了critical area,即临界区域;其本质是不管多少个线程同步来访问这段代码,对于单个线程来说,其执行是有序的且是确定的。synchronized是一个排它锁,一旦上锁之后,执行在单个线程执行完毕之后,释放锁,然后其他线程才可以进入该临界区域。从而保证了其代码执行顺序是确定和有序的。

7.  代码指令重排

   代码示例如下:

class OrderExample {
int a = 0;
boolean flag = false;

public void writer() {
    a = 1;                   
    flag = true;           
}

public void reader() {
    if (flag) {                
        int i =  a +1;      
        ……
    }
}
}
设想如下情况: 线程A首先执行writer()方法, 线程B线程接着执行reader()方法,线程B在int i=a+1 是不一定能看到a已经被赋值为1,因为在writer中,两句话顺序可能打乱。

线程A的结果:flag=true  a=1

线程B的结果:flag=true(此时a=0)

大家从这里可以看到,synchronize解决的问题是保证指令的顺序性,以及解决代码执行过程中的不确定性。

   

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值