[CountDownLatch] 1.实现等待TcpServer启动完毕后再发射事件 2.使用CompletableFuture代替CountDownLatch将阻塞封装到内部

背景:

        有时候,我们希望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!
 */

总结:可以看出来,是单独的线程启动,但是可以控制住顺序了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值