问题:
- spring mvc controller线程安全吗?
- 引申servlet及struts1/2的Action线程安全吗?
知识点:
- 实例变量和类变量(静态变量)
- 类&单实例&多实例(如何知道一个类有多少个实例)
- 线程名称&线程安全
- spring mvc controller单实例OR多实例 web容器启动时区别
- synchronized使用
实验:
-
实验方法:
通过是否使用@Scope(“prototype”)注解来控制controller单&多实例;
通过类变量进行实例计数跟踪;
通过实例变量验证单&多实例线程安全;
通过synchronized更直观体验多线程并发访问; -
实验代码:
服务器代码:
package com.test;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
*
* @author : Ares.yi
* @createTime : 2016-04-10 上午11:13:42
* @version : 1.0
* @description :
*
*/
@Controller
@RequestMapping("/hello")
@Scope("prototype")
public class TestController {
/**类变量 实例个数计数器*/
private static int instanceCount = 0;
/**实例变量*/
private /**volatile*/ String instanceVar;
/**代码块锁,替换this等变量synchronized方法Or代码块同步*/
private /**static*/ byte[] lock = new byte[0];
public TestController() {
instanceCount++;
log("\tCreate 'TestController' new instance->"+instanceCount);
}
/**
* 测试
*
* @param reqOrder:请求顺序
* @param name:请求名称(客户端使用多线程访问时可以使用线程名称)
* @param sleepMillis:通过用户请求时随机传递一个时间值来模拟服务器处理不同用户时所需不同耗时
* @return
*
* @author : Ares.yi
* @createTime : 2016年04月10日 下午3:25:41
*/
@RequestMapping(value="test",produces = "application/json; charset=UTF-8")
@ResponseBody
public String test(int reqOrder,String name,long sleepMillis){
long now = System.currentTimeMillis();
simulateUseTimeConsuming(sleepMillis);
long end = System.currentTimeMillis();
/**注意比较单&多实例时,多线程并发访问影响*/
instanceVar = name;
String s = " reqOrder:"+reqOrder+",instance:"+instanceCount+",instanceVar:"+instanceVar+"-->{sleep:"+sleepMillis+"ms,useTime:"+(end-now)+"ms,name:"+name+"}";
log(s);
return s;
}
/**
* 耗时模拟
* 注意:单&多实例在多线程访问时,体验synchronized对用户并发访问的影响
* @param sleepMillis
*
* @author : Ares.yi
* @createTime : 2016年04月10日 下午2:46:54
*/
private /**synchronized*/ void simulateUseTimeConsuming(long sleepMillis){
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private <T> void log(T t){
System.out.println("\t"+Thread.currentThread().getName()+"==>"+t);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
客户端模拟代码(wget或浏览器手动请求一样):
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.xxx.common.tools.CommonUtils;
import com.xxx.common.tools.web.WebUtils;
/**
*
* @author : Ares.yi
* @createTime : 2016年04月10日 下午3:25:41
* @version : 1.0
* @description :
*
*/
public class Test {
/**
* @param args
*
* @author : Ares.yi
* @createTime :2016年04月10日 下午4:25:41
*/
public static void main(String[] args) {
/**m个线程,模拟n个用户并发请求 (尝试着分别调整为不同的值)*/
int m = 10 , n = 100;
ExecutorService pool = Executors.newFixedThreadPool(m);
for(int i = 0 ; i < n ;i++){
final int order = i;
pool.execute(new Runnable() {
@Override
public void run() {
/**随机0.3~2秒 调整更长时间直观体验并发访问直接的影响*/
long millis = CommonUtils.getRandomNumber(300, 2000);
try {
String threadName = Thread.currentThread().getName();//获取当前线程名称
String url = "http://localhost:70/springmvc-example/hello/test?reqOrder="
+order+"&name="+threadName
+"&sleepMillis="+millis;
System.out.println(threadName+"==>"+WebUtils.fetchContent(url));
} catch (Exception e) {
//e.printStackTrace();
System.err.println(order+"==>"+e.getMessage()+" force sleep:"+millis);
}
}
});
}
System.out.println("shut down");
// System.exit(-1);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 实验收货:
1.spring mvc 默认controller是单实例(通过注解@Scope(“prototype”)变了多实例);
2.单实例时非线程安全,不要在controller中定义成员变量(实例变量);
3.单实例时,web容器启动时便开始实例化controller,全局唯此实例,每次访问都使用此实例响应;
4.多实例时,每一次访问,基本&多数(发现偶尔也会重复使用实例)会产出新实例对应响应;
5.单实例时,并发请求,访问synchronized同步方法时,彼此阻塞影响(synchronized方法实例锁);
6.多实例时,并发请求,访问synchronized同步方法时,彼此不影响(synchronized方法实例锁);
建议(此为一次给内部团队分享):
- 亲自动手各种调整代码体验
- 通过此典型多线程环境多玩玩synchronized、ThreadLocal等
原博客地址:http://blog.csdn.net/LoveJavaYDJ/article/details/53584396