运用多线程技术处理并发客户端请求
- 服务器端主线程:
- 创建一个
ServerSocket
实例,绑定到指定端口,用于监听客户端连接请求。
- 使用
while (true)
循环持续监听客户端连接。当有客户端连接时,调用 serverSocket.accept()
方法,该方法会阻塞直到有新的客户端连接进来,此时返回一个 Socket
实例代表与客户端的连接。
- 为每个客户端创建独立线程:
- 每当
accept()
方法返回一个新的 Socket
时,创建一个新的线程来处理该客户端的通信。通常的做法是创建一个实现了 Runnable
接口的类,在其 run()
方法中编写与客户端通信的逻辑,如读取客户端发送的数据、处理数据并向客户端发送响应。
- 在主线程中,使用
new Thread(new ClientHandler(socket)).start()
来启动新线程,其中 ClientHandler
是实现了 Runnable
接口的类,构造函数接收 Socket
实例。
- 客户端处理线程逻辑:
- 在实现
Runnable
接口的类的 run()
方法中,通过 Socket
获取输入输出流,如 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))
和 PrintWriter out = new PrintWriter(socket.getOutputStream(), true)
分别用于读取客户端输入和向客户端输出。
- 使用循环读取客户端发送的数据,处理数据(例如业务逻辑计算等),然后将处理结果通过输出流返回给客户端。
- 处理完通信后,关闭相关的流和
Socket
连接。
可能遇到的线程安全问题及解决方法
- 共享资源竞争:
- 问题描述:如果多个线程访问和修改共享资源(如全局变量、静态变量等),可能会导致数据不一致。例如,一个线程正在读取某个共享变量的值,同时另一个线程修改了该值,可能导致读取到不一致的数据。
- 解决方法:
- 使用
synchronized
关键字:可以修饰方法或代码块。修饰方法时,该方法在同一时间只能被一个线程访问;修饰代码块时,只有获取到指定对象锁的线程才能执行该代码块。例如:
private static int sharedVariable;
public static synchronized void incrementSharedVariable() {
sharedVariable++;
}
- **使用 `Lock` 接口**:`java.util.concurrent.locks.Lock` 提供了比 `synchronized` 更灵活的锁机制。例如 `ReentrantLock`,使用时先创建锁实例,在需要同步的代码块前调用 `lock()` 方法获取锁,代码块结束后调用 `unlock()` 方法释放锁。
private static int sharedVariable;
private static final ReentrantLock lock = new ReentrantLock();
public static void incrementSharedVariable() {
lock.lock();
try {
sharedVariable++;
} finally {
lock.unlock();
}
}
- 死锁:
- 问题描述:两个或多个线程相互等待对方释放资源,导致所有线程都无法继续执行。例如,线程A持有资源1并等待资源2,而线程B持有资源2并等待资源1。
- 解决方法:
- 避免嵌套锁:尽量减少锁的嵌套使用,如果无法避免,确保所有线程以相同顺序获取锁。
- 设置锁超时:使用
tryLock()
方法尝试获取锁,并设置超时时间。如果在超时时间内未获取到锁,线程可以选择放弃或执行其他操作,避免无限等待。
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 处理共享资源
} catch (InterruptedException e) {
// 处理中断异常
} finally {
lock.unlock();
}
} else {
// 未获取到锁的处理逻辑
}
- 线程局部变量误用:
- 问题描述:
ThreadLocal
用于为每个线程创建独立的变量副本。如果在使用 ThreadLocal
时逻辑错误,例如错误地共享了本应线程局部的变量,可能导致数据混乱。
- 解决方法:
- 正确使用
ThreadLocal
,确保每个线程独立地操作自己的变量副本。例如,在需要为每个线程维护独立的数据库连接时,可以这样使用 ThreadLocal
:
private static final ThreadLocal<Connection> connectionThreadLocal = ThreadLocal.withInitial(() -> {
try {
return DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
- 在使用完 `ThreadLocal` 变量后,及时调用 `remove()` 方法清理,避免内存泄漏。例如,在处理完数据库操作后:
connectionThreadLocal.remove();