java内存区域
按是否共享
- 线程共享数据区:堆、方法区
- 线程隔离数据区:程序计数器、本地方法栈、虚拟机栈
程序计数器:可以理解为当前线程所执行字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)
虚拟机栈与本地方法栈:每个java方法在执行时都会创建一个栈帧用于存储局部变量、操作数栈、动态链接等信息。只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
堆:存放对象实例
方法区1:它用于存储已被虚拟机加载的类型信息、常量2、静态变量3、即时编译器编译后的代码缓存等数据(《Java虚拟机规范》说是堆的一个逻辑分区,暂且认为他是放在和堆一个地方)
运行时常量池:属于方法区的一部分,存放编译后生成的各种字面量和符号引用(这也是class文件常量池的内容)
并发问题
① 当一个单例实例对象有成员变量时,多线程访问会出现数据问题。
② 当访问一个单例实例对象方法有局部变量时,线程是安全的。
第一点解释:
class c {
public int num;
public void method(int i){
if(i == 1){
num = 10;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a is " + num);
}else {
num = 20;
System.out.println("b is " + num);
}
}
}
假设线程a和线程b同时访问实例c的method(i)方法,线程a的i=1,线程b的i=2,当线程a执行到7行,线程b执行到第13行,num=20,a的num原本是10的,结果被更改了。(掘金还不支持行号显示,从class c { 算第一行开始)
第二点解释:
class c {
public void method(int i){
int num = 0;
if(i == 1){
num = 10;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a is " + num);
}else {
num = 20;
System.out.println("b is " + num);
}
}
}
这里需要有java内存区域的知识点,上一节讲过。
- 对象实例保存在堆中,局部变量保存在虚拟机栈中,堆是共享内存区域,而虚拟机栈是隔离区域,因此各个线程之间不会影响。
- 方法内部的变量为方法私有的变量,其生存周期随着方法的结束而终结。
spring mvc中bean的作用域及并发问题
作用域
spring 3 中bean的作用域有5种
| 作用域(scope) | 解释 |
|---|---|
| singleton | 单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是Spring中的缺省作用域 |
| prototype | 原型模式,每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的bean4使用prototype作用域,而对无状态的bean5使用singleton作用域 |
| request | 在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁。 |
| session | 在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求内有效,请求结束,则实例将被销毁。 |
| global Session | 在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。 |
Spring并发访问的线程安全性问题
-
由于Spring MVC默认是Singleton的,所以会产生一个潜在的安全隐患。根本核心是instance变量保持状态的问题。这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果:
- 我们不用每次创建Controller
- 减少了对象创建和垃圾收集的时间
-
由于只有一个Controller的instance,当多个线程同时调用它的时候,它里面的instance变量就不是线程安全的了,会发生窜数据的问题。
解决办法:
- 在控制器中不使用实例变量
- 将控制器的作用域从单例(singleton)改为原型(prototype)
- 在Controller中使用ThreadLocal变量6
死锁
由于多线程的访问,会出现一些意想不到的问题,常见的数据问题。通常会进行加锁。加锁就会出现另一种问题-死锁
定义:多个并发进程因争夺系统资源而产生相互等待的现象。
必要条件
互斥、占有且等待、不可抢占、循环等待
参考资料:
Footnotes
-
jdk1.7及之前hotspot虚拟机对方法区的实现为永久代,jdk1.7把字符串常量池、静态变量等从方法区移除;jdk1.8移除了永久代,用本地内存实现的元空间代替,也就是对方法区的实现为元空间 ↩
-
jdk1.7之前方法区常量包括字符串常量池、运行时常量池、;jdk1.7把字符串常量池移到堆中。 ↩
-
jdk1.7之后静态变量从方法区移除 ↩
-
有状态bean(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。一般是prototype scope。 ↩
-
无状态对象(Stateless Bean),就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。一般是singleton scope。 ↩
-
线程本地变量 ↩