Netty自定义逻辑线程池实现相同角色的数据修改串行化执行和与work线程分离

前言:

目前在写一个简易版的新人游戏服务器demo,使用的是netty框架,由于netty是Reactor模型,由boss线程池进行对于套接字是否就绪的监听,work线程池对于io流进行操作。前面一直将游戏逻辑放在了work线程池中处理,这样子如果遇到一些耗时大的逻辑会影响netty框架对于io流的读取。这里就将新建一个逻辑线程池进行逻辑处理,并且串行化执行对于同一个角色的数据修改。

问题:

为什么要串行化对于角色数据修改?

对于游戏来说,角色的血量、蓝量。这些可能会被其他角色的攻击、buffer、技能等等所修改,所以这些共享的变量通常都需要注意并发修改。最普遍的做法就是加上锁。
对此有另一种更加好的做法。让角色血量、蓝量的数据修改串行化执行。这样子就不会存在并发问题,并且只需加上voliate关键字。即可让其他线程也可以获取到最新的值。

为什么将业务逻辑执行与work线程池分离?

work线程池是netty框架对于io流进行读写的线程组。将游戏逻辑放在了work线程池中处理,这样子如果遇到一些耗时大的逻辑会影响netty框架对于io流的读取

如何实现?

前提:

ChannelHandlerContext.write的源码

private void write(Object msg, boolean flush, ChannelPromise promise) {
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }

Netty允许在非NIO线程中写消息的。如果当前是在NIO线程,就直接写过去,如果不在NIO线程,写消息操作会被封装成一个task,然后再由NIO线程池来处理。
这样子就给了我们操作空间了,我们只需要在自身的逻辑线程池中wirte()即可让netty自动的转移到work线程组中执行。

功能实现:

结构图

在这里插入图片描述

主要是通过channel的hashcode后对于线程组数量求余获取其下标,每次通过下标提交到不同线程中的任务队列里即可。一个channel对应一个角色,所以每次该角色的数据修改都在同一个线程中,根据任务队列先进先出实现串行化执行。
2021-2-7 修改:为了保证用户在同一个客户端登陆多个用户,以及切换角色,最终修改为采用playId为求余下标。

代码实现:

自定义线程池接口:

/**
 * 自定义线程池接口
 * @author lqhao
 */
public interface ThreadPool<Job extends Runnable> {
    /**
     * 执行一个Job,这个Job需要实现Runnabl
     */
    void execute(Job job,Integer index);

    /**
     * 关闭线程池
     */
    void shutdown();

}

逻辑线程池的实现&内部线程类

/**
 * 逻辑线程池的实现
 * @author lqhao
 */
public class LogicThreadPool<Job extends Runnable>implements ThreadPool<Job> {

    private static LogicThreadPool instance;
    private int threadSize;
    public static void init(int num){
        LogicThreadPool logicThreadPool=new LogicThreadPool();
        instance=logicThreadPool;
        instance.initializeWorkers(num);
        instance.threadSize=num;
    }

    public int getThreadSize() {
        return threadSize;
    }

    public void setThreadSize(int threadSize) {
        this.threadSize = threadSize;
    }
    public static LogicThreadPool getInstance() {
        return instance;
    }
    /**
     * 工作者列表
     */
    private final CopyOnWriteArrayList<Worker> workers=new CopyOnWriteArrayList<>();
    /**
     *线程编号生成
     */

    private AtomicLong threadNum = new AtomicLong();

    /**
     * 初始化线程工作者
     */
    private void initializeWorkers(int num) {
        for (int i = 0; i < num; i++) {
            Worker worker = new Worker();
            workers.add(worker);
            Thread thread = new Thread(worker, "ThreadPool-Worker-" + threadNum.incrementAndGet());
            thread.start();
        }
    }
    class Worker implements Runnable {
        private final Logger log = LoggerFactory.getLogger(ServerHandler.class);
        private volatile boolean running = true;
        private LinkedList<Job> tasks = new LinkedList<>();

        @Override
        public void run() {
            Job job = null;
            while (running) {
                //如果线程中的工作队列空了,就wait
                synchronized (tasks) {
                    while (tasks.isEmpty()) {
                        try {
                            tasks.wait();
                        } catch (InterruptedException ex) {
                            // 感知到外部对WorkerThread的中断操作,返回
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    // 取出一个Job
                    job = tasks.removeFirst();
                }
                if (job != null) {
                    try {
                        job.run();
                    } catch (Exception ex) {
                        log.error("System.out...... -> " + ex);
                    }
                }
            }
        }
        public void shutdown() {
            running = false;
        }
    }

    @Override
    public void execute(Job job,Integer index) {
        if (job != null) {
            // 添加一个工作,然后进行通知
           //根据channel的HashCode取余
            Worker worker=workers.get(index);
            synchronized (worker.tasks) {
                worker.tasks.addLast(job);
                //通知线程有任务了
                worker.tasks.notifyAll();
            }
        }
    }
    @Override
    public void shutdown() {
        for (Worker worker : workers) {
            worker.shutdown();
        }
    }

}

这里需要处理的是对于任务队列的出队入队都需要并发安全处理,即便它和线程是一对一的关系,但是还是存其他线程往里面放任务的情况,还是会有并发情况。
调用代码:

		//根据channel计算index
        Integer index= CommonsUtil.getIndexByChannel(ctx.channel());
        //放入逻辑线程池中执行 参数Job、index
        LogicThreadPool.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    dispatcherservlet.handler(request,ctx.channel());
                } catch (InvalidProtocolBufferException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        },index);

总结:

这部分如果一开始来写可能会忽略掉任务队列的并发安全,不过之前看《java并发艺术》这本书的时候有写过一个相关的demo,这里的部分代码都是参考了那本书。串行化执行,这是一种新的解决并发安全的方法吧。减少了锁的使用,但是可能会造成的影响就是逻辑线程池里部分线程繁忙,而部分线程一直空闲。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值