java运行时的内存分布与线程同步

前言

在学习线程的时候,确定会遇到线程同步问题java

多个并发线程之间按照某种机制协调前后次序执行,当有一个线程在对内存进行操做时,其余线程都不能够对这个内存地址进行操做,直到该线程完成操做编程

根据上面的定义咱们能够知道,线程同步问题主要是多线程并发状况下如何互斥访问共享资源;在多个线程对同一变量进行读写操做时,若是没有原子性,就可能产生脏数据。实现线程同步有不少的方式,好比同步方法,锁,阻塞队列等。bash

那么java中的线程是如何访问资源的呢,在学习了一段时间的jvm后,对于线程访问共享资源有了更深的理解多线程

JVM的内存布局

Java虚拟机在运行Java程序时,会把它所管理的内存区域划分为若干个不一样的区域,每一个区域都有各自的用途;下图是经典的JVM内存布局 并发

jvm内存模型

线程私有区域
  • 程序计数器 (PC): 当前线程所执行的字节码的行号指示器,用于线程切换后能恢复到正确的执行位置jvm

  • Java虚拟机栈: 每一个线程都有一个本身的Java栈, 描述了Java方法运行时的内存模型:每一个方法在执行时都会建立一个栈帧用于储存局部变量表、操做数栈、动态连接、方法出口等信息;每一个方法的从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程ide

  • 本地方法栈:与JAVA栈相似,区别是使用的对象不同,本地方法栈是给Native方法使用的,JAVA虚拟机栈是给JAVA方式使用的函数

线程共享区域
  • 堆区(GC堆): 他是JVM所管理的内存中最大的一块,用于存放new处来的对象实例,被全部线程所共享;由垃圾回收器回收
  • 方法区: 全部线程共享一个内存区;他用于存储已被虚拟机加载的类元信息,静态变量,常量(JDK8后字符串常量已经被移至堆内存),JIT编译后的代码等;Java虚拟机规范规定能够不对方法区进行垃圾回收,当并非不回收,主要看具体虚拟机的实现,好比能够回收一些废弃常量和无用的类;

多线程访问共享内存

多个线程同时执行同一个方法的时候,出现异常结果的状况:

在没有任何同步机制的状况下,多个线程共享一块内存区域,对其操做;布局

获得正常结果的状况:
  • 不使用共享内存,每一个线程内存空间相互独立;学习

  • 多线程共享一块内存区域,可是对这块共享区域加锁访问;

例子

1. 在没有任何同步机制的状况下,多个线程共享一块内存区域,对其操做

对静态变量sum进行操做,静态变量保存在线程共享的内存区域中,多个线程能够同时访问

package com.thread;

/**
 * @Author: ranjun
 * @Date: 2019/7/22 14:13
 */
public class AddTest {

    private static int sum = 0;

    /**
     * 对静态变量sum进行累加操做
     * @param n
     * @return
     */
    public static int sum(int n){
        sum = 0;
        for(int i = 0; i <= n; i++){
            sum += i;
            //让出cpu资源,让其余线程执行;
            //能够将下面这段注释掉,若是累加次数足够少时,仍然会获得正确结果(当前线程能够在当前时间片中完成累加操做,并返回值)
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return sum;
    }
}

复制代码

写个main函数,定义四个线程,每一个线程都调用上面的静态方法,观察运行结果,基本都是错误的:

package com.thread;

/**
 * @Author: ranjun
 * @Date: 2019/7/22 14:17
 */
public class MainTest {

    public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println(Thread.currentThread().getName() +":" +AddTest.sum(100));
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}


复制代码

部分运行结果,都是错的

Thread-3:20153
Thread-1:19953
Thread-2:19953
Thread-0:20153
Thread-1:18510
Thread-3:18510
复制代码

缘由:多个线程同时对静态全局变量s进行操做致使;

ps:这里的例子是静态全局变量s,其实有不少种状况会引发结果异常问题,如在main方法中new出了一个对象,new出来的对象是存放在堆中的,多个线程共享,此时若是多线程同时操做该对象的话,也是有可能产生错误结果;

2. 不使用共享内存,每一个线程内存空间相互独立

修改静态sum方法,使用局部变量sum,以下:

public static int sum(int n){
        int sum = 0;
        for(int i = 0; i <= n; i++){
            sum += i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return sum;
    }
复制代码

运行程序,结果正确:

Thread-3:5050
Thread-1:5050
Thread-2:5050
Thread-0:5050
Thread-0:5050
Thread-1:5050
Thread-2:5050
Thread-3:5050
复制代码
3. 多线程共享一块内存区域,可是对这块共享区域加锁访问
public synchronized static int sum(int n){
            sum = 0;
            for (int i = 1; i <= n; i++) {
                sum += i;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return sum;
    }
复制代码

运行程序,结果正确:

Thread-1:5050
Thread-3:5050
Thread-2:5050
Thread-0:5050
Thread-2:5050
复制代码

总结

从上面的例子能够看到,多线程对共享资源的操做结果是难以控制的;因此在多线程编程过程当中,咱们要知道哪些资源是线程共享的,哪些是线程本地私有的,而后合理的对共享资源进行协调。

相关文章
相关标签/搜索