Java线程模型的理解
2016-03-24 00:00
330 查看
摘要: 一直以来对于Java的内存模型以及在线程的参与下的内存模型的理解都是介于在懂与不懂之间,今天和付老师的争论让我对这一部分的内容有了更加深刻的理解,写下来以加深印象,有错误的地方希望大家指正
我对于Java的内存模型以及有多线程参与的背景下的内存模型有了更加深入的认识,感谢付老师。
好了话不多说,直接上代码说明问题
如下图是Java的内存模型,图可能和你看的不一样,但是该有的部分还都是有的,那我们先来看看jvm的内存模型
链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类
型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他
与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。其中64 位长度的long 和double 类型的数据会占用2 个局部
变量空间(Slot),其余的数据类型只占用1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在
帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小
以上的内容引用自 《深入理解java虚拟机》一书
那么我依据上面的理解,那么输出的内容肯定是要被覆盖的呀,但是结果并没有
那么他是如何实现线程安全的呢:
借助上面的例子来讲
上面的例子当中有三个线程
main(主)--主线程
thread1-------子线程
thread2-------子线程
从运行的结果了看他们各自运行出了各自的运行的结果,我们对线程在加以理解,线程是程序运行的基本单位,那么也就意味着他有一套完整的内存体系
那么thread1和thread2也有着各自的内存区域,他们首先从主线程中拷贝一份single实例,然后把它们各自线程内需要运行的方法也都拷贝一份,这样你就可以理解
为何程序的结果没有被覆盖,
哈赛,问题又来了,依据上面的解释,Java是天生的线程安全那还需要加锁作甚,真是个善于思考的boy(-_-)
依然回到上面的例子当中,不知道你们发现没有还有一个全局的变量flag,他既不在thread1的线程当中,也不再thread2的线程当中,他在他们共同的父线程当中,
所以做为子线程可以对他进行修改,但是这样便可以引发线程安全问题,所以对于这样的公共资源和临界资源是需要我们去加锁的,如果全部都是方法域的变量我们是没有
必要去加锁的
现在我得出的结论是,我用spring可以安心的用了
前言
争论的开始是由于我在模仿着写一个ioc,可是里面的单例付老师说会有问题,于是有了下面的验证性的例子的出现,之前我对于Java的内存模型介于懂与不懂之间,通过这次的争论之后我对于Java的内存模型以及有多线程参与的背景下的内存模型有了更加深入的认识,感谢付老师。
好了话不多说,直接上代码说明问题
这个是多个线程访问的一段代码:
package com.fkxuexi.demo; public class Single { boolean flag = false; private Single(){}; public static Single single = new Single(); public static Single getInstance(){ return single; } public void singleMethod(String name){ String dest = name; if(!flag){ flag = true; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.err.println(dest); } } }
客户端的多线程的测试代码
package com.fkxuexi.demo; public class ValidateThreadSecurityDemo { public static void main(String[] args) { Single single = Single.getInstance(); new Thread(){ public void run(){ single.singleMethod("this is a thread1 called method1"); } }.start(); new Thread(){ public void run(){ single.singleMethod("this is a thread2 called method2"); } }.start(); } }
以下是测试的截图部分
问题
其实到这里问题就来了,为什么同一个对象,两个线程去调用其结果值没有被覆盖,起初我很高兴因为我做的那个ioc注入的实例就是单例的不会引发线程的安全问题,但是紧接着问题就浮现在了我的脑海,如下图是Java的内存模型,图可能和你看的不一样,但是该有的部分还都是有的,那我们先来看看jvm的内存模型
方法区
中会存放已经被jvm加载的类信息、常亮、静态变量、即时编译器编译后的代码等数据,他是供所有的线程所共享的jvm栈
虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类
型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他
与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。其中64 位长度的long 和double 类型的数据会占用2 个局部
变量空间(Slot),其余的数据类型只占用1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在
帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小
以上的内容引用自 《深入理解java虚拟机》一书
那么我依据上面的理解,那么输出的内容肯定是要被覆盖的呀,但是结果并没有
原因:
在争论的过程中,有一句话付老师重复了很多遍:Java是天生线程安全的,当初的确听到过这样的一句话那么他是如何实现线程安全的呢:
借助上面的例子来讲
上面的例子当中有三个线程
main(主)--主线程
thread1-------子线程
thread2-------子线程
从运行的结果了看他们各自运行出了各自的运行的结果,我们对线程在加以理解,线程是程序运行的基本单位,那么也就意味着他有一套完整的内存体系
那么thread1和thread2也有着各自的内存区域,他们首先从主线程中拷贝一份single实例,然后把它们各自线程内需要运行的方法也都拷贝一份,这样你就可以理解
为何程序的结果没有被覆盖,
哈赛,问题又来了,依据上面的解释,Java是天生的线程安全那还需要加锁作甚,真是个善于思考的boy(-_-)
依然回到上面的例子当中,不知道你们发现没有还有一个全局的变量flag,他既不在thread1的线程当中,也不再thread2的线程当中,他在他们共同的父线程当中,
所以做为子线程可以对他进行修改,但是这样便可以引发线程安全问题,所以对于这样的公共资源和临界资源是需要我们去加锁的,如果全部都是方法域的变量我们是没有
必要去加锁的
延伸一
tomcat中的servlet也是单例的,延伸二spring中管理的controller、业务处理层、数据访问层全部是单例的会不会出现线程安全问题呢
这个可以参考上面的demo,相信你可以得出结论,现在我得出的结论是,我用spring可以安心的用了
相关文章推荐
- 一个通过struts2获取多选框(checkbox)的坑
- Java 并发工具包 java.util.concurrent 用户指南
- Java入门编程(作业)Time3实现总结(一)
- Git 和 Github安装和使用,关联eclipse
- java.lang.IllegalArgumentException出错原因
- 《小平工作日志》java抛异常、捕异常的简单介绍
- 《小平工作日志》java异常中嵌套异常的捕获方法
- spring+hibernate+maven项目结构搭建
- Spring3.0中AOP生成代理对象问题
- Spring源码分析1--IoC容器载入Bean定义资源
- DataTables的服务器端分页处理
- Spring EL 小记(一)
- JAVA后台获取不到form表单提交值的情况
- 20145326 《Java程序设计》第4周学习总结
- java安装
- java 使用ReentrantLock Condition实现阻塞队列
- Struts2常见的struts.xml以及constant属性的写法(设置清空页面缓存)。
- Java——Swing事件处理
- 229. Majority Element II | Java最短代码实现
- 学习java的第一天