背景:
有时候,我们希望NettyServer启动时不能说卡住主线程。
也不能说:直接就启动一个线程,不然没办法发射出“服务器启动”这个事件。
这时就可以使用此类执行完毕后,通知下主线程。
---------------------CountDownLatch版本
BeanConfig.java
package com.example.demo.config;
import com.example.demo.net.TcpServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
@Bean
public TcpServer tcpServer() {
return new TcpServer();
}
}
TcpServer.java
package com.example.demo.net;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TcpServer extends Thread {
private final CountDownLatch latch = new CountDownLatch(1);
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " TcpServer start!!!");
latch.countDown();
}
public void syncInit() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
DemoApplication.java
package com.example.demo;
import com.example.demo.net.TcpServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class DemoApplication {
public static ApplicationContext context;
public static void main(String[] args) throws Exception {
context = SpringApplication.run(DemoApplication.class, args);
initTcpServer();
System.out.println(Thread.currentThread().getName() + " done");
}
public static void initTcpServer() {
context.getBean(TcpServer.class).start();
context.getBean(TcpServer.class).syncInit();
}
}
/*
2024-03-09 23:11:23.106 INFO 8472 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 11.0.11 on DESKTOP-JTMBOEI with PID 8472 (D:\2_test_java\demo\target\classes started by Administrator in D:\2_test_java\demo)
2024-03-09 23:11:23.109 INFO 8472 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2024-03-09 23:11:23.814 INFO 8472 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.203 seconds (JVM running for 2.622)
2024-03-09 23:11:23.816 INFO 8472 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
2024-03-09 23:11:23.817 INFO 8472 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
Thread-2 TcpServer start!!!
main done
*/
---------------------CompletableFuture版本
TcpServer.java
package com.jn.gameserver.core.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@Slf4j
public class TcpServer extends Thread {
private CompletableFuture<Boolean> runFuture = new CompletableFuture<>();
private final int port;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
public TcpServer(int port) {
this.port = port;
}
@Override
public void run() {
try {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
// 负责业务处理
GatewayHandler gatewayHandler = new GatewayHandler();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new Encoder())
.addLast(new Decoder())
.addLast(gatewayHandler);
}
});
ChannelFuture cf = bootstrap.bind(port).sync();
log.info("TcpServer start at port={}", port);
runFuture.complete(true);
} catch (Exception e) {
log.error("TcpServer error=", e);
System.exit(1);
}
}
public void syncInit() {
try {
runFuture.get();
} catch (InterruptedException | ExecutionException e) {
log.error("error=", e);
}
}
}
Main.java
package com.jn.gameserver;
import com.jn.gameserver.config.GameConfig;
import com.jn.gameserver.core.LogicManagerService;
import com.jn.gameserver.core.ThreadManager;
import com.jn.gameserver.core.netty.TcpServer;
import com.jn.gameserver.core.utils.SpringBeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
@Slf4j
@SpringBootApplication
public class GameserverApplication {
public static void main(String[] args) {
SpringApplication.run(GameserverApplication.class, args);
// 关服钩子
regShutdownHook();
// 线程池
initThreadManager();
// 初始化各个模块
initLogicManager();
// tcp服务器
initTcpServer();
log.info("gs start success, enter `quit` exit !!!");
// 阻塞关服
waitExit();
}
private static void initThreadManager() {
ThreadManager.init();
}
private static void initLogicManager() {
SpringBeanUtil.getBean(LogicManagerService.class).init();
}
private static void initTcpServer() {
TcpServer tcpServer = new TcpServer(GameConfig.tcpPort);
tcpServer.start();
tcpServer.syncInit();
}
private static void waitExit() {
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
String str = scanner.nextLine();
if (str.equals("quit")) {
// 退出虚拟机
System.exit(1);
}
}
}
}
private static void regShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.warn("gs shutdown!!!");
}, "gs-shutdown-hook-thread"));
}
}
理解:
可以看出来,这个也是一次线程交互。
---------------------旧的-----------------------
1)TcpServer.java
package org.example.testStart;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
public class TcpServer extends Thread{
// 使用这个在当前线程执行完毕后,立马通知主线程
private final CountDownLatch latch;
public TcpServer(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " tcpserver start success" );
latch.countDown();
// 相当于netty等待关闭
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2)Main.java
package org.example.testStart;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
regHookThread();
System.out.println(Thread.currentThread().getName() + " start!");
initTcpServer();
initTcpServer();
System.out.println(Thread.currentThread().getName() + " end!");
// 等待5s,模拟使用jmx关闭服务器
TimeUnit.SECONDS.sleep(5);
System.exit(1);
}
public static void initTcpServer() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
new TcpServer(latch).start();
// 等待执行完毕
latch.await();
}
public static void regHookThread() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " hook thread finish!");
}, "gs-hook-thread"));
}
}
/*
main start!
Thread-0 tcpserver start success
Thread-1 tcpserver start success
main end!
gs-hook-thread hook thread finish!
*/
总结:可以看出来,是单独的线程启动,但是可以控制住顺序了。