Java并发,主内存、工作内存是什么?

什么是主内存,工作内存

        这2个概念是Java内存模型(Java Memory Model)中提出的,我们目前只需要知道内存模型是帮我们屏蔽底层硬件细节的,程序员只需要按照它的规则来写代码,写的程序就可以实现跨平台运行,很巧妙的设计。

了解了内存模型,我们回到主题,我们知道JVM将内存划分了以下几大块

  • 堆 (进程内所有线程共享)

  • 方法区 (进程内所有线程共享)

  • 虚拟机栈 (每个线程独立)

  • native本地方法栈 (每个线程独立)

  • pc计数器 (每个线程独立)

那主内存,工作内存跟它们的对应关系是怎么样的呢? 这里直接给出结论。

  • 主内存就是堆 + 方法区

  • 工作内存就是虚拟机栈 + native本地方法栈 + pc计数器

        这个知识点看似不起眼,但是却很重要,因为只有有了这个结论,才能与我们后面的实际代码例子结合起来,否则就会感觉理论与实际操作脱节了,没法对应起来。

举个例子:

有一个Counter计数器类,内部有一个count成员变量int类型,记录当前的总数,具体定义如下。

public class Counter {
    private int count;
    public void increment() {
        this.count++;
    }
    public int getCount() {
        return this.count;
    }
}

        我们现在的任务是调用一亿次increment方法,然后打印count的数量,那么显然正确的输出应该是一亿。

public static void main(String[] args) {
   // 单线程代码
   Counter counter = new Counter();
   int loopCount = 100000000;
   long startTime = System.currentTimeMillis();
   for (int i = 0; i < loopCount; i++) {
       counter.increment();
   }
   System.out.println("count:" + counter.getCount());  
   System.out.println("take time:" + (System.currentTimeMillis() - startTime) + "ms");
}

// 输出结果
count:100000000
take time:577ms

        这样的代码我们太熟悉了,但是这次我想从代码在虚拟机栈中的具体执行过程来加深理解程序是怎么运作的。先通过Javac和Javap命令查看Counter类的increment方法的字节码实现。

javac Counter.java
javap -verbose Counter.class

// Counter类 increment方法的字节码
  public void increment();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field count:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field count:I
        10: return

        我们知道Java中方法的调用是基于栈帧实现的,每个栈帧中主要包含操作数栈+用到类的运行时常量池引用+本地变量表。我画了一张示意图帮助大家理解整个执行过程,并且将其中一次count++操作的字节码在操作数栈中的执行步骤分解了(以count=10为例),这里要注意,下面这张图的运行是在工作内存中(main方法所在线程的虚拟机栈中)。

        ​过程其实比较简单,我们写的代码在底层就这样运行着,是不是一点点兴奋了。 好,到这里,我要总结方法论了!上面的代码之所以在单线程中运行正确,因为满足了以下三个条件!

  • 循环从0到一亿,是严格按顺序执行的(有序性)

  • 循环过程中,前一次对count的修改对后面可见(可见性)

  • 因为是严格按顺序执行的,所以count++操作中间不会交叉执行,所以其实在单线程环境中,可以认为满足原子性 (原子性)

        上面的条件只要有一个被打破,执行的结果就可能不正确,这也就是为什么Java多线程环境下容易出现并发问题,原因就是没有同时满足这三个条件。

多线程为什么会出现并发问题?

        上面已经提到,我们上面的Count类的实例中,需同时满足 有序性,可见性,原子性,其中有序性和原子性,我们比较容易想到因为多个线程交叉执行,如果不加同步控制,有序性和原子性肯定没法保证,但是这里比较难理解的是可见性,骨头先捡难啃的啃,所以下一次我们先来谈谈可见性。

Java学习视频

Java基础:

Java300集,Java必备优质视频_手把手图解学习Java,让学习成为一种享受

Java项目:

【Java游戏项目】1小时教你用Java语言做经典扫雷游戏_手把手教你开发游戏

【Java毕业设计】OA办公系统项目实战_OA员工管理系统项目_java开发

猜你喜欢

转载自blog.csdn.net/java_0000/article/details/125151420