多线程使用的主要目的在于:
一、吞吐量:你作WEB,容器帮你作了多线程,可是他只能帮你作请求层面的。简单的说,可能就是一个请求一个线程。或多个请求一个线程。若是是单线程,那同时只能处理一个用户的请求。
二、伸缩性:也就是说,你能够经过增长CPU核数来提高性能。若是是单线程,那程序执行到死也就利用了单核,确定没办法经过增长CPU核数来提高性能。
鉴于你是作WEB的,第1点可能你几乎不涉及。那这里我就讲第二点吧。
--举个简单的例子:
假设有个请求,这个请求服务端的处理须要执行3个很缓慢的IO操做(好比数据库查询或文件查询),那么正常的顺序多是(括号里面表明执行时间):
a、读取文件1 (10ms)
b、处理1的数据(1ms)
c、读取文件2 (10ms)
d、处理2的数据(1ms)
e、读取文件3 (10ms)
f、处理3的数据(1ms)
g、整合一、二、3的数据结果 (1ms)
单线程总共就须要34ms。
那若是你在这个请求内,把ab、cd、ef分别分给3个线程去作,就只须要12ms了。
因此多线程不是没怎么用,而是,你日常要善于发现一些可优化的点。而后评估方案是否应该使用。
假设仍是上面那个相同的问题:可是每一个步骤的执行时间不同了。
a、读取文件1 (1ms)
b、处理1的数据(1ms)
c、读取文件2 (1ms)
d、处理2的数据(1ms)
e、读取文件3 (28ms)
f、处理3的数据(1ms)
g、整合一、二、3的数据结果 (1ms)
单线程总共就须要34ms。
若是仍是按上面的划分方案(上面方案和木桶原理同样,耗时取决于最慢的那个线程的执行速度),在这个例子中是第三个线程,执行29ms。那么最后这个请求耗时是30ms。比起不用单线程,就节省了4ms。可是有可能线程调度切换也要花费个一、2ms。所以,这个方案显得优点就不明显了,还带来程序复杂度提高。不太值得。
那么如今优化的点,就不是第一个例子那样的任务分割多线程完成。而是优化文件3的读取速度。
多是采用缓存和减小一些重复读取。
首先,假设有一种状况,全部用户都请求这个请求,那其实至关于全部用户都须要读取文件3。那你想一想,100我的进行了这个请求,至关于你花在读取这个文件上的时间就是28×100=2800ms了。那么,若是你把文件缓存起来,那只要第一个用户的请求读取了,第二个用户不须要读取了,从内存取是很快速的,可能1ms都不到。
伪代码:java
1
2
3
4
5
6
7
8
9
10
11
|
public
class
MyServlet
extends
Servlet{
private
static
Map<String, String> fileName2Data =
new
HashMap<String, String>();
private
void
processFile3(String fName){
String data = fileName2Data.get(fName);
if
(data==
null
){
data = readFromFile(fName);
//耗时28ms
fileName2Data.put(fName, data);
}
//process with data
}
}
|
看起来好像还不错,创建一个文件名和文件数据的映射。若是读取一个map中已经存在的数据,那么就不不用读取文件了。
但是问题在于,Servlet是并发,上面会致使一个很严重的问题,死循环。由于,HashMap在并发修改的时候,多是致使循环链表的构成!!!(具体你能够自行阅读HashMap源码)若是你没接触过多线程,可能到时候发现服务器没请求也巨卡,也不知道什么状况!
好的,那就用ConcurrentHashMap,正如他的名字同样,他是一个线程安全的HashMap,这样能轻松解决问题。web
1
2
3
4
5
6
7
8
9
10
11
|
public
class
MyServlet
extends
Servlet{
private
static
ConcurrentHashMap<String, String> fileName2Data =
new
ConcurrentHashMap<String, String>();
private
void
processFile3(String fName){
String data = fileName2Data.get(fName);
if
(data==
null
){
data = readFromFile(fName);
//耗时28ms
fileName2Data.put(fName, data);
}
//process with data
}
}
|
这样真的解决问题了吗,这样虽然只要有用户访问过文件a,那另外一个用户想访问文件a,也会从fileName2Data中拿数据,而后也不会引发死循环。
但是,若是你以为这样就已经完了,那你把多线程也想的太简单了,骚年!
你会发现,1000个用户首次访问同一个文件的时候,竟然读取了1000次文件(这是最极端的,可能只有几百)。What the fuckin hell!!!
难道代码错了吗,难道我就这样过个人一辈子!
好好分析下。Servlet是多线程的,那么
数据库
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
MyServlet
extends
Servlet{
private
static
ConcurrentHashMap<String, String> fileName2Data =
new
ConcurrentHashMap<String, String>();
private
void
processFile3(String fName){
String data = fileName2Data.get(fName);
//“偶然”-- 1000个线程同时到这里,同时发现data为null
if
(data==
null
){
data = readFromFile(fName);
//耗时28ms
fileName2Data.put(fName, data);
}
//process with data
}
}
|
上面注释的“偶然”,这是彻底有可能的,所以,这样作仍是有问题。
所以,能够本身简单的封装一个任务来处理。缓存
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
|
public
class
MyServlet
extends
Servlet{
private
static
ConcurrentHashMap<String, FutureTask> fileName2Data =
new
ConcurrentHashMap<String, FutureTask>();
private
static
ExecutorService exec = Executors.newCacheThreadPool();
private
void
processFile3(String fName){
FutureTask data = fileName2Data.get(fName);
//“偶然”-- 1000个线程同时到这里,同时发现data为null
if
(data==
null
){
data = newFutureTask(fName);
FutureTask old = fileName2Data.putIfAbsent(fName, data);
if
(old==
null
){
data = old;
}
else
{
exec.execute(data);
}
}
String d = data.get();
//process with data
}
private
FutureTask newFutureTask(
final
String file){
return
new
FutureTask(
new
Callable<String>(){
public
String call(){
return
readFromFile(file);
}
private
String readFromFile(String file){
return
""
;}
}
}
}
|
以上全部代码都是直接在bbs打出来的,不保证能够直接运行。安全
多线程最多的场景:web服务器自己;各类专用服务器(如游戏服务器);
多线程的常见应用场景:
一、后台任务,例如:定时向大量(100w以上)的用户发送邮件;
二、异步处理,例如:发微博、记录日志等;
三、分布式计算服务器