java socket & Nio 之 利用线程池改进

前面描述了BIO中采用1对1模式的服务器架构,发展它不适合高并发,高性能的服务器业务需求,那么接下来我们采用一个改进版来改进一下这个结构,这个结构主要是改进服务器端的程序。改进的措施如下:
1.在服务器接收到客户端的每个连接后,把客户端的socket包装成成功一个Task。
2.在服务器端根据系统的资源建一个固定大小的线程池和Task队列,然后线程池去管理处理线程的执行。
经过上面的处理,我们可以规避1对1的模式,还能固定线程的创建,从而避免服务器资源被消耗的危险。
框架图如下:
这里写图片描述
代码结构如下:
public class FakeNameServer {
public static final int PORT = 8080;
public static void main(String[] args) throws IOException {
ServerSocket server = null;
try {
server = new ServerSocket(PORT);
Socket socket = null; //创建线程池和消息队列都是有界的,因此无论有多少个客户并发连接数量有多大,都不会把系统资源消耗完,指定线程池和任务队列大小。
NameServerHandlerExecutePool singleExecutor = new NameServerHandlerExecutePool(100, 1000);
while (true) {
socket = server.accept();
//把任务交给线程执行器
singleExecutor.execute(new HandlerClientRunnable(socket));
}
} finally {
if (server != null) {
server.close();
server = null;
}
}
}
}
//客户端链路层处理代码逻辑
public class HandlerClientRunnable implements Runnable {
public static final String NAME_1 = “A”;
public static final String NAME_2 = “B”;
private Socket socket;
public HandlerClientRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
//包装socket的输入流
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
//包装socket的输出流
out = new PrintWriter(this.socket.getOutputStream(), true);
String content = null;
while (true) {
//不断的读取客户端发送过来的数据
System.out.println(“read client send content”);
content = in.readLine();
//如果读到的数据为空,就继续读
if (content == null) {
System.out.println(“read client send content is null “);
break;
}
System.out.println(“read client send content: ” + content);
//如果读到数据是NAME_1
if (content.equalsIgnoreCase(NAME_1)) {
out.println(“Hello ” + NAME_1 + ” ” + new Date(System.currentTimeMillis()));
} else {
out.println(“Hello ” + NAME_2 + ” ” + new Date(System.currentTimeMillis()));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//先关闭socket的输入流
if (in != null) {
try {
in.close();
in = null;
} catch (IOException e) {
e.printStackTrace();
}
}
//先关闭socket的输出流
if (out != null) {
out.close();
out = null;
}
//关闭socket
if (socket != null) {
try {
socket.close();
socket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//创建一个固定的线程池和任务队列
public class NameServerHandlerExecutePool {
private ExecutorService executor;
public NameServerHandlerExecutePool(int maxPoolSize, int queueSize) {
executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue(queueSize));
}
public void execute(Runnable task) {
executor.execute(task);
}
}
总结:上述结构虽然在一定程度上解决上一篇文章提成的问题,但是其还是不能适应高并发的服务器需求,因为内部通信机制还是同步阻塞的。java socket 对输入流进行读取操作是时候,基本是一直阻塞下去的。如果发生下面的事情,它就会执行下去。
1.发现socket有数据可以读
2.可用数据已经读取完毕
3.发现其他的io异常
从这个几个现象基本可以分析出问题所在:
1.socket 输入流:当发送请求的一边因为网络环境的影响导致读取输入流通信的一边将一直阻塞,在此过程中其他接入的消息只能在messge queue 上排队等候。
2.socket输出流:当调用输出流调用write方法写输出流的时候,这个过程也是阻塞的,知道所有的字节全部写入完毕,或者发现IO异常 ,在这个过程中如果接受数据的那边处理缓慢,将导致数据不能及时的从缓存去读取出来,这也将导致发送方的buffer不断减小,最后无法写入数据,这个时候通讯双方将进入keep-alive状态。
从上面分析得出:socket读和写是同步阻塞的,阻塞的时间取决于通信双方IO线程处理速度和网络IO的速度。
从另外一方面分析:1.线程池实现也是采用阻塞队列实现的,当队列满了以后,后续入队列的操作也将被阻塞。2:由于队列的前端采用一个Acceptor线程来接收客户端的接入,当队列满后,新的客户端接入将被拒绝,这样新的客户端连接将全部超时。各种原因导致该架构无法适应高并发的网络服务器,那么我们该怎么解决这个问题呢,下篇文字讲NIO的实现,大家分析一下NIO的原理,然后它是怎么解决之前的弊端的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值