八、多线程为什么会出现安全问题

前言:

  在前面我们主要介绍了一下线程的创建,一些枯燥的概念,以及线程间如何通信和多线程存在线程安全的问题,那么为什么多线程在执行的时候会造成安全问题呢,这一问题我们并没有深入的进入下去,下面我们来了解一下所谓的线程安全倒地时怎么来的。

一、内存模型简述

  java内存模型之前专门写过一篇总结,虽然都是拿网上的资料东拼西凑的,也多次的去掌握这方面知识,但一些东西还是不了解,网上有很多资料,这里不久过多赘述,知识简单的描述一下。

  了解的同学都知道java内存模型被分为了五个区域,程序计数器、堆、虚拟机栈、本地方法栈以及方法区,理论上方法区也是属于堆中的一部分,只不过方法区是堆中的一块永久区域,也就是垃圾回收不是很频繁,但绝不是不进行垃圾回收,而堆中的垃圾回收则相对频繁的进行,我们稍微来看一下五个区域的作用,下面上网上的一张分区图。

  

  程序计数器:

    我们都知道线程之间的执行时并发的,既然是并发的那就存在频繁切换,如果线程A执行一段代码执行到一半,线程抢到了CPU执行权,当线程B执行完后,如何能够找到线程A被抢断执行到的位置呢,这就是计数器的作用。

    也就因此每个线程都对一个程序计数器,且是该线程独享的,也就是私有(若不是私有的则找不到被打断是哪个线程的哪一行代码),而计数器就记录了线程正在执行的内存地址,以确保被打断是能够回到原来的地方再次执行。

  虚拟机栈:

    虚拟机栈也叫本地方法栈,在java虚拟机中每一个线程都会对应一个栈,也就是说一个线程在执行时在创建程序计数器的同时会创建一个栈,

    每个java虚拟机栈则是由多个栈帧组成的,而每个栈帧则对应了一个方法(通常来说线程里面调用的都是多个方法,那么这里面的每一个方法都对应一个栈帧),栈帧在方法运行时,创建并入栈,方法执行完毕后该栈帧会弹出栈帧中的元素作为返回值,并且栈帧也会被清除,而虚拟机栈中主要存放局部变量和引用型变量,因为是每一个线程都会创建一个栈,所以是私有的。

  方法区:

    方法区则是java堆中的永久区域,主要存放了一些类的信息,例如:类的全限定名、修饰符,方法名称,static静态常量(注:指定义就已经赋值)、final类型的常量,类中的字段名称,方法的参数等,因此方法区是所有线程共享的。

  常量池: 

    常量池是属于方法区的一块内存,又分为运行时常量池和静态常量池,这里就不细说了;

    常量池中存储的数据可以分成两部分,一类是是字面量(指:字符串、final常量等)、另一类则是引用量(指:类、接口,方法和字段名称以及描述),常量池则是在编译期间就已经确定被保存在已经编译好的.class文件中

  本地方法栈:

    本地方法栈和虚拟机栈类似,只不过它是专门用来执行native方法的,而虚拟机栈是执行java的方法的,而native方法通常调用的是java一些最底层的东西,例如C语言等;

  堆内存:

    堆内存用于存放由new创建的对象和数组,每个实体都有一个内存地址值,实体中的变量都有默认初始化值,即使实体不在使用了,不把实体的引用指向null,垃圾回收也不会执行回收

二、线程安全问题的分析:

  上面我们大致了解了一下内存的分布,那么多线程在运行过程是在哪一块出了问题,而导致输出的结果不正确的,来更具传智播客老师讲课的一张图来分析一下流程:

  

 

 

   如上如果我们写了一个MoneyDemo类,里面有一个main方法,来看一下它的执行过程会使什么样的:

    第一步:那么类加载器会将MoneyDemo类的.class文件加入到内存中,方法区会存入我们的类的全限定名、修饰关键字、里面的方法名称、参数、返回值等,

    第二步:java的类执行引擎就会去方法区找到我们的MoneyDemo类的信息,找要启动的方法就是main方法,为main方法创建虚拟机栈和程序计数器,同时将new Object()则是存入到堆内存中个,而Object obj 则会被存入栈内;

    第三步:在java虚拟机栈找到main方法的栈帧后,请求CUP资源,那么虚拟机就会将java虚拟机栈当前要执行main方法的信息存入高速缓存寄存器中,CUP获取高速缓存内的信息去执行代码。

    第四步:若这个时候我们Object对象进行更改,更改的信息会先存入高速缓存,然后根据栈帧找到虚拟机栈中的main方法,然后将更改的内容同步不到堆内存中,

    那么这时候问题就出现了,如果有两个线程A和B,线程A正修改了Object对象的内容,还没有来的即同步到主内存,而线程B却在读堆中的Object对象,那么这时候读出来的信息就是错误的数据,至此我们的多线程为什么会出现安全问题也就找到了。

三、结论:

    看到这里想必都明白了,多线程下出现安全问题,就是存在多条线程操作同一数据,而CPU执行时先将主内存中的数据拷贝一份作缓存,之后的操作不在从请求主内存中拿数据而是从缓存中获取数据,如果执行完后有更改再同步回主内存,这时候如果存在多条线程操作,就会出现主内存和缓存中数据的不一致,而导致了最终结果的错误,而单线程情况下则完全不会出现这一情况。    

 

posted @ 2019-09-10 19:32  は問わない  阅读(3273)  评论(0编辑  收藏  举报