5.3使用JDK类库提供的线程池
java.util.concurrent包提供了现成的线程池的实现
健壮,而且功能也更强大。如图3-4所示是线程池的类框图。
图3-4 JDK类库中的线程池的类框图
Executor 接口表示线程池,它的execute(Runnable task)方法用来执行Runnable 类
型的任务。Executor 的子接口ExecutorService 中声明了管理线程池的一些方法,比如
用于关闭线程池的shutdown()方法等。Executors 类中包含一些静态方法,它们负责生
成各种类型的线程池ExecutorService实例
package multithread3;
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class EchoServer {
private int port=8000;
private ServerSocket serverSocket;
private ExecutorService executorService; //线程池
private final int POOL_SIZE=4; //单个CPU时线程池中工作线程的数目
public EchoServer() throws IOException {
serverSocket = new ServerSocket(port);
//创建线程池
//Runtime 的availableProcessors()方法返回当前系统的CPU的数目
//系统的CPU越多,线程池中工作线程的数目也越多
executorService= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * POOL_SIZE);
System.out.println("服务器启动");
}
public void service() {
while (true) {
Socket socket=null;
try {
socket = serverSocket.accept();
executorService.execute(new Handler(socket));
}catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String args[])throws IOException {
new EchoServer().service();
}
}
/** 负责与单个客户通信的任务,代码与3.6.1 节的例程3-5 的Handler类相同 */
class Handler implements Runnable{…}
在EchoServer 的构造方法中,调用Executors.newFixedThreadPool()创建了具有固
定工作线程数目的线程池。在EchoServer 的service()方法中,通过调用executor-
Service.execute()方法,把与客户通信的任务交给了ExecutorService线程池来执行。
使用线程池的注意事项
虽然线程池能大大提高服务器的并发性能,但使用它也会存在一定风险。与所有
多线程应用程序一样,用线程池构建的应用程序容易产生各种并发问题,如对共享资
源的竞争和死锁。此外,如果线程池本身的实现不健壮,或者没有合理地使用线程池,
还容易导致与线程池有关的死锁、系统资源不足和线程泄漏等问题。
1.死锁
任何多线程应用程序都有死锁风险。造成死锁的最简单的情形是,线程A持有对
象X 的锁,并且在等待对象Y 的锁,而线程B 持有对象Y 的锁,并且在等待对象X
的锁。线程A与线程B都不释放自己持有的锁,并且等待对方的锁,这就导致两个线
程永远等待下去,死锁就这样产生了。
虽然任何多线程程序都有死锁的风险,但线程池还会导致另外一种死锁。在这种情
形下,假定线程池中的所有工作线程都在执行各自任务时被阻塞,它们都在等待某个任
务A 的执行结果。而任务A 依然在工作队列中,由于没有空闲线程,使得任务A 一直
不能被执行。这使得线程池中的所有工作线程都永远阻塞下去,死锁就这样产生了。
2.系统资源不足
如果线程池中的线程数目非常多,这些线程会消耗包括内存和其他系统资源在内
的大量资源,从而严重影响系统性能。
3.并发错误
线程池的工作队列依靠wait()和notify()方法来使工作线程及时取得任务,但这两
个方法都难于使用。如果编码不正确,可能会丢失通知,导致工作线程一直保持空闲
状态,无视工作队列中需要处理的任务。因此使用这些方法时,必须格外小心,即便
是专家也可能在这方面出错。最好使用现有的、比较成熟的线程池。例如,直接使用
java.util.concurrent包中的线程池类。
4.线程泄漏
使用线程池的一个严重风险是线程泄漏。对于工作线程数目固定的线程池,如果
工作线程在执行任务时抛出 RuntimeException 或Error,并且这些异常或错误没有被
捕获,那么这个工作线程就会异常终止,使得线程池永久失去了一个工作线程。如果
所有的工作线程都异常终止,线程池就最终变为空,没有任何可用的工作线程来处理
任务。
导致线程泄漏的另一种情形是,工作线程在执行一个任务时被阻塞,如等待用户
的输入数据,但是由于用户一直不输入数据(可能是因为用户走开了),导致这个工作
线程一直被阻塞。这样的工作线程名存实亡,它实际上不执行任何任务了。假如线程
池中所有的工作线程都处于这样的阻塞状态,那么线程池就无法处理新加入的任务了。
5.任务过载
当工作队列中有大量排队等候执行的任务时,这些任务本身可能会消耗太多的系
统资源而引起系统资源缺乏。
综上所述,线程池可能会带来种种风险,为了尽可能避免它们,使用线程池时需
要遵循以下原则。
(1)如果任务A 在执行过程中需要同步等待任务B的执行结果,那么任务A 不
适合加入到线程池的工作队列中。如果把像任务A 一样的需要等待其他任务执行结果
的任务加入到工作队列中,可能会导致线程池的死锁。
(2)如果执行某个任务时可能会阻塞,并且是长时间的阻塞,则应该设定超时
时间,避免工作线程永久的阻塞下去而导致线程泄漏。在服务器程序中,当线程等待
客户连接,或者等待客户发送的数据时,都可能会阻塞。可以通过以下方式设定超时
时间:
l 调用ServerSocket的setSoTimeout(int timeout)方法,设定等待客户连接的超时
时间,参见本章3.5.1 节(SO_TIMEOUT选项);
l 对于每个与客户连接的Socket,调用该Socket的setSoTimeout(int timeout)方
法,设定等待客户发送数据的超时时间,参见本书第2 章的2.5.3 节
(SO_TIMEOUT选项)。
(3)了解任务的特点,分析任务是执行经常会阻塞的I/O 操作,还是执行一直不
会阻塞的运算操作。前者时断时续地占用CPU,而后者对CPU具有更高的利用率。预
计完成任务大概需要多长时间?是短时间任务还是长时间任务?
根据任务的特点,对任务进行分类,然后把不同类型的任务分别加入到不同线程
池的工作队列中,这样可以根据任务的特点,分别调整每个线程池。
(4)调整线程池的大小。线程池的最佳大小主要取决于系统的可用CPU的数目,
以及工作队列中任务的特点。假如在一个具有 N 个CPU的系统上只有一个工作队列,
并且其中全部是运算性质(不会阻塞)的任务,那么当线程池具有 N 或 N+1 个工作
线程时,一般会获得最大的 CPU 利用率。
如果工作队列中包含会执行I/O 操作并常常阻塞的任务,则要让线程池的大小超
过可用CPU的数目,因为并不是所有工作线程都一直在工作。选择一个典型的任务,
然后估计在执行这个任务的过程中,等待时间(WT)与实际占用CPU 进行运算的时
间(ST)之间的比例WT/ST。对于一个具有N 个CPU 的系统,需要设置大约N×
(1+WT/ST)个线程来保证CPU得到充分利用。
当然,CPU 利用率不是调整线程池大小过程中唯一要考虑的事项。随着线程池中
工作线程数目的增长,还会碰到内存或者其他系统资源的限制,如套接字、打开的文
件句柄或数据库连接数目等。要保证多线程消耗的系统资源在系统的承载范围之内。
(5)避免任务过载。服务器应根据系统的承载能力,限制客户并发连接的数目。
当客户并发连接的数目超过了限制值,服务器可以拒绝连接请求,并友好地告知客户:
服务器正忙,请稍后再试。
分享到:
相关推荐
- **Socket的使用**:客户端通过`Socket(String host, int port)`建立到服务器的连接,`connect()`方法指定服务器地址和端口。 3. **数据交换** - **输入/输出流**:Socket通信基于字节流。服务器端使用`Socket....
在"Java Socket 视频流转发Socket"这个主题中,我们将深入探讨如何使用Java Socket来处理视频流的传输。 首先,让我们了解什么是ServerSocket。在Java中,`java.net.ServerSocket`类是服务器端使用的,它监听特定...
这个Java Socket 聊天通信演示代码将涵盖上述基本概念,帮助初学者理解如何使用Java实现网络通信。实际的代码会包含更多的细节,如错误处理、多线程管理和用户界面交互等。通过学习和实践这个示例,你可以进一步提升...
这个"Java Socket使用实例"提供了一个基础的网络通信模板,通过学习和实践,你可以进一步了解Socket通信的原理,掌握在网络环境下进行数据交换的方法。在实际项目中,Socket通信常用于构建分布式系统、实时聊天应用...
在Java编程语言中,Socket是实现网络通信的基础组件,它为两台计算机之间的通信提供了低级别的接口。在本文中,我们将深入探讨Java Socket...通过理解Socket的工作原理和使用方法,你可以创建出功能强大的网络客户端。
Java Socket网络编程是Java平台中的核心特性,它为开发者提供了在TCP/IP协议下创建网络应用的能力。Socket编程主要用于实现客户端和服务器之间的通信,基于客户机/服务器模型。在这个模型中,服务器端通常处于被动...
三、Java Socket 编程的应用场景 Java Socket 编程有广泛的应用场景,例如: 1. 网络游戏:使用 Java Socket 编程可以实现网络游戏中的实时通信和数据传输。 2..instant messaging:使用 Java Socket 编程可以...
这个经典版本可能包含了一系列关于如何有效使用Java Socket进行网络通信的实例和最佳实践。 首先,让我们深入了解Java Socket的基本概念。Socket通常被称为套接字,它是两台计算机之间通信的端点。在TCP/IP协议族中...
Java Socket是Java编程语言中用于网络通信的核心API,它提供了低级别的、面向连接的、基于TCP/IP协议的网络通信服务。本教程将深入探讨Java Socket的相关知识点,旨在帮助开发者理解和掌握如何在Java环境中构建网络...
在Java中,我们可以使用`HttpServletRequest`对象的`getHeader()`方法来获取这个值。 ```java HttpServletRequest request = ...; // 获取HttpServletRequest实例 String clientIp = request.getHeader("X-...
本教程将深入探讨Java Socket编程的概念、用法以及实际应用中的关键点。 Java Socket提供了低级别的网络通信接口,允许应用程序创建和维护与其他主机上的应用程序之间的连接。Socket基于TCP/IP协议,保证了数据的...
在Java中,Socket类位于`java.net`包下,提供了多种构造函数和方法来实现网络连接的建立、数据交换以及连接的关闭。 1. **连接服务器** - 建立连接:客户端通常需要知道服务器的IP地址或域名以及服务端口。在Java...
Java Socket实用教程主要涵盖的是Java中的网络编程技术,特别是如何使用Socket进行客户端和服务端的通信。Socket在Java中是实现TCP/IP通信的基础,它提供了进程间网络通信的能力。本教程可能包括以下几个关键知识点...
在本示例中,我们将深入探讨如何使用Java Socket进行图片的传输,这对于理解网络编程和数据交换至关重要。"Java Socket传输图片源代码"这个主题涵盖了以下几个关键知识点: 1. **Java Socket基础**: - Socket是...
Java Socket是Java编程语言中用于实现网络通信的核心API,它基于TCP/IP协议栈,提供了低级别的网络连接功能。Socket在Java中被广泛用于构建客户端-服务器应用,例如创建Web服务器、聊天应用程序、文件传输等。以下是...
三、Socket编程流程 1. 客户端:创建Socket,指定服务器的IP地址和端口,建立连接。 2. 服务端:监听特定端口,接受客户端连接,创建Socket。 3. 双方:通过Socket的输入输出流进行数据交换。 4. 结束:当通信完成后...
本话题主要关注如何使用Java Socket实现数据的收发,并在客户端断网或者服务器重启后实现自动重连功能。 1. **Java Socket基础** - **Socket类**:在Java中,`java.net.Socket`类代表一个TCP连接,它封装了输入/...
在这个"java socket传输demo"中,我们将深入探讨Java如何使用Socket类来实现客户端和服务器端之间的数据交换。 首先,Socket是Java提供的一个类,位于`java.net`包下,它代表了网络上的两个应用程序之间的一个双向...
客户端通常使用Socket类来建立到服务器的连接,并通过Socket的`getInputStream()`和`getOutputStream()`方法获取输入/输出流,进行数据的发送和接收。 ```java Socket socket = new Socket("localhost", 8080); ...
本篇文章将深入讲解如何通过三步学习Java Socket编程。 **第一步:理解Socket** Socket,又称为“套接字”,是网络通信中的一个抽象概念,它封装了IP地址和端口号,是网络通信链路的句柄。在Java中,Socket和...