杂记复习笔记
ThreadLocal
1. ThreadLocal是如何为每个线程创建变量的副本的:
1). 首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,
value为变量副本(即T类型的变量)。
2). 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,
以ThreadLocal要保存的副本变量为value,存到threadLocals。
3). 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
2. set方法中会获取当前线程的threadLocals,然后当前ThreadLocal(假设为:threadLocal)作为key,value值作为threadLocals的value值。因为threadLocals是每个线程私有的,所以不同线程取值时,
虽然都调用threadLocal.get(),当get方法回去不同的线程中取不同的threadLocals,然后取到各个线程中的value值。
3. 最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。
数据库:
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
Session管理:
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
4. ThreadLocal变量为静态的,保证每个线程在任何地方都可以使用相同的key值。
如何避免死锁:
产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁:
死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。
死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。
避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。
常用的避免死锁的方法:
1、有序资源分配法
2、银行家算法
解决死锁问题的策略:
1、条件一:互斥条件
条件一念一否定的,因为资源的互斥性是由其自身的性质决定的。但是可以采用虚拟设备技术能排除非共享设备死锁的可能。
2、条件二:不剥夺条件
很难实现。系统一般让资源占有者自己主动释放资源,而不是采用抢占的方式。
3、条件三:占有并等待
在资源分配策略上可以采取静态的一次性资源分配的方法来保证死锁不可能发生,这是一种很保守的静态预防死锁的方法,但是资源利用率低下。
4、条件四:环路条件
在进行资源分配前检查是否会出现环路,预测是否可能发生死锁,只要有这种可能就不予以分配。即采用动态分配资源的方法。
总结来看解决死锁的策略有以下几个:
1、采用资源静态分配方法预防死锁。
2、采用资源动态分配、有效的控制分配方法来避免死锁。
3、当死锁发生时检测出死锁,并设法修复。
Web服务器总的来说提供服务的方式有三种:
-
多进程方式
-
多线程的方式
-
异步方式
其中效率最高的是异步的方式,最稳定的是多进程方式,占用资源较少的是多线程的方式。
1. 多进程
此种架构方式中,web服务器生成多个进程并行处理多个用户请求,进程可以按需或事先生成。有的web服务器应用程序为每个用户请求生成一个单独的进程来进行响应,不过,一旦并发请求数量达到成千上万时,多个同时运行的进程将会消耗大量的系统资源。(即每个进程只能响应一个请求或多个进程对应多个请求)
优点:
-
最大的优势就在于稳定性,一个进程出错不会影响其它进程。如,服务器同时连接100个请求对就的是100个进程,其中一个进程出错,只会杀死一个进程,还有99个进程继续响应用户请求。
-
每个进程响应一个请求
缺点:
-
进程量大,进程切换次数过多,导致CPU资源使用效率低
-
每个进程的地址空间是独立的,很多空间中重复的数据,所以内存使用效率低
-
进程切换由于内核完成,占用CPU资源
2. 多线程
在多线程方式中,每个线程来响应一下请求,由于线程之间共享进程的数据,所以线程的开销较小,性能就会提高。
优点:
-
线程间共享进程数据
-
每个线程响应一个请求
-
线程切换不可避免(切换量级比较轻量)
-
同一进程的线程可以共享进程的诸多资源,比如打开的文件
-
对内存的需求较之进程有很大下降
-
读可以共享,写不可以共享
缺点:
-
线程快速切换时会带来线程抖动
-
多线程会导致服务器不稳定
3. 异步方式
一个进程或线程响应多个请求,不需要任何额外开销的,性能最高,占用资源最少。但也有问题一但进程或线程出错就会导致整个服务器的宕机。
IO模型:
1. 同步阻塞IO(Blocking IO):即传统的IO模型。用户线程在内核进行IO操作时被阻塞。用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收
的数据拷贝到用户空间,完成read操作。即用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,
不能做任何事情,对CPU的资源利用率不够。
2. 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK。这样做用户线程可以在发起IO请求后可以立即返回。由于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。但
并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。
即用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要
不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
3. IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。
同一个线程内同时处理多个IO请求。IO多路复用模型使用了Reactor设计模式实现了这一机制。
4. 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。
“真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,
数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。
转自:http://www.cnblogs.com/fanzhidongyzby/p/4098546.html
AIO:
CompletionHandler 后续处理
Future.get() 获取结果