第四章 进阶
1、多线程
(1)继承Thread类,复写run()方法,实例化。(只适用于没有继承其他类时)
(2)实现Runnable接口,实现run(),把实例化的对象传递给Thread构造函数
启动 start()
2、Logger 写日志,线程安全,可以在并行运行的不同线程中调用它的方法。
Logger.getLogger()静态工厂方法可获取一个Logger实例,同名字的Logger是同一个实例。
并发服务器的两种方法
1、 一客户一线程
为每个连接都创建一个新的线程来处理
while (true) {
Socket clntSock = servSock.accept(); // Block waiting for connection
// Spawn thread to handle new connection
Thread thread = new Thread(new EchoProtocol(clntSock, logger));
thread.start();
logger.info("Created and started Thread " + thread.getName());
}
(EchoProtocol实现了Runnable接口)
2、 线程池
每个线程都会消耗资源,限制总线程数,并重复使用。
在服务器启动时,创建固定数量的线程池。
具体来说:服务器创建一个ServerSocket实例,再创建N个线程,每个线程反复循环,从共享的ServerSocket实例接受客户端连接(这里demo用的匿名内部类),当多线程都调用同一个ServerSocket实例的accept()方式时,它们都阻塞等待,知道新的一个连接成功建立。
行为上类似:一组迭代服务器。
// Create a server socket to accept client connection requests
final ServerSocket servSock = new ServerSocket(echoServPort);
final Logger logger = Logger.getLogger("practical");
// Spawn a fixed number of threads to service clients
for (int i = 0; i < threadPoolSize; i++) {
Thread thread = new Thread() {
public void run() {
while (true) {
try {
// Wait for a connection
Socket clntSock = servSock.accept();
// Handle it 这里是静态方法
EchoProtocol.handleEchoClient(clntSock, logger);
} catch (IOException ex) {
logger.log(Level.WARNING, "Client accept failed",
ex);
}
}
}
};
thread.start();
logger.info("Created and started Thread = " + thread.getName());
}
线程池的调度:
线程池的大小最好能根据负载情况进行调整,在系统负载增加时扩展线程池大小,Java的调度工具:Executor接口。
Executor接口:代表了一个根据某种策略来执行Runnable实例的对象,其中可能包含了排队、调度的细节,或如何选择要执行的任务。通常使用 Executor 而不是显式地创建线程。需实现execute()方法。
ExecutorService 接口继承于Executor接口,并提供了更高级的工具来关闭服务器。
Executors类的各种静态工厂方法,获取 ExecutorService 实例。
// Create a server socket to accept client connection requests
ServerSocket servSock = new ServerSocket(echoServPort);
Logger logger = Logger.getLogger("practical");
Executor service = Executors.newCachedThreadPool(); // Dispatch svc
// Run forever, accepting and spawning threads to service each
// connection
while (true) {
Socket clntSock = servSock.accept(); // Block waiting for connection
service.execute(new EchoProtocol(clntSock, logger));
}
execute()方法:要么将其分配给一个已有的线程,要么创建一个新线程。
修改调度策略:使用Executors类的不同的静态工厂方法,比如:
固定大小线程池:newFixedThreadPool
(int nThreads)
单一线程池:newSingleThreadExecutor
()