【CompletableFuture完成游戏服务器中各类业务线程间的交互】1.玩家线程和好友线程的交互 2.玩家线程和排行榜线程的交互 3.supplyAsync(带返回值的)和runAsync

FriendService.java

package com.example.testsb.friend;

import com.example.testsb.role.Role;
import com.example.testsb.role.RoleService;
import com.example.testsb.thread.ThreadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

@Service
public class FriendService {
    @Autowired
    private ThreadService threadService;

    @Autowired
    private RoleService roleService;

    /**
     * 由于好友数据由好友线程维护,查询的话,交给好友线程,查询出来的一定是pb数据,这样子防止修改
     *
     * @param roleId
     * @return
     */
    public CompletableFuture<List<Integer>> queryFriends(int roleId) {
        return CompletableFuture.supplyAsync(() -> {
            // 由于好友数据由好友线程维护读写,因此交给好友线程查询
            Role role = roleService.getRoleByRoleId(roleId);

            // 查询出来的是pb数据,防止被修改。 这里暂时先new一个代替
            return new ArrayList<>(role.getFriends());
        }, threadService.getFriendThread());
    }

    /**
     * 添加逻辑必须在好友线程,添加的结果,随意修改就行
     *
     * @param roleId1
     * @param roleId2
     * @return
     */
    public CompletableFuture<Boolean> addFriend(int roleId1, int roleId2) {
        return CompletableFuture.supplyAsync(() -> {
            // 由于好友数据由好友线程维护读写,因此
            Role role1 = roleService.getRoleByRoleId(roleId1);
            Role role2 = roleService.getRoleByRoleId(roleId2);

            if (role1.getFriends().contains(roleId2)) {
                return false;
            }

            if (role2.getFriends().contains(roleId1)) {
                return false;
            }

            role1.getFriends().add(roleId2);
            role2.getFriends().add(roleId1);

            return true;
        }, threadService.getFriendThread());
    }
}

Role.java

package com.example.testsb.role;


import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class Role {
    private int roleId;
    private List<Integer> friends = new ArrayList<>();

    public Role(int roleId) {
        this.roleId = roleId;
    }
}

RoleService.java

package com.example.testsb.role;

import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class RoleService {
    private final Map<Integer, Role> roleId2RoleMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        roleId2RoleMap.put(1, new Role(1));
        roleId2RoleMap.put(2, new Role(2));
    }

    public Role getRoleByRoleId(int roleId) {
        return roleId2RoleMap.get(roleId);
    }
}

ThreadService.java

package com.example.testsb.thread;

import jakarta.annotation.PostConstruct;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.springframework.stereotype.Service;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class ThreadService {

    private final ExecutorService[] logicThreadArr = new ExecutorService[10];

    private final ExecutorService friendThread = Executors.newSingleThreadExecutor(new TaskThreadFactory("friendThread", false, Thread.NORM_PRIORITY));

    @PostConstruct
    public void init() {
        for (int i = 0; i < logicThreadArr.length; i++) {
            logicThreadArr[i] = Executors.newSingleThreadExecutor();
        }
    }

    public ExecutorService getLogicThread(Object hashObj) {
        int code = Math.abs(hashObj.hashCode());
        return logicThreadArr[code % logicThreadArr.length];
    }

    public ExecutorService getFriendThread() {
        return friendThread;
    }
}

Main.java

package com.example.testsb;

import com.example.testsb.friend.FriendService;
import com.example.testsb.thread.ThreadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@Slf4j
@SpringBootApplication
public class TestSbApplication {

    public static void main(String[] args) throws Exception {
        ApplicationContext context = SpringApplication.run(TestSbApplication.class, args);

        final int roleId1 = 1;
        final int roleId2 = 2;

        // 尝试查询好友
        for (int i = 0; i < 10; i++) {
            int roleId = i % 2 == 0 ? roleId1 : roleId2;
            context.getBean(ThreadService.class).getLogicThread(roleId).execute(() -> {
                try {
                    FriendService friendService = context.getBean(FriendService.class);

                    CompletableFuture<List<Integer>> future = friendService.queryFriends(roleId);

                    List<Integer> friends = future.get();
                    log.info("roleId={},friends={}", roleId, friends);
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            });
        }

        // 模拟任意一个线程请求添加好友,那也是没有关系,因为读取和写入逻辑都是在好友线程,因此没有什么问题
        // 别的线程只是允许读取一下而已,就算读取完,后面又被好友线程修改了,那也没有什么问题。
        for (int i = 0; i < 10; i++) {
            context.getBean(ThreadService.class).getLogicThread(i).execute(() -> {
                FriendService friendService = context.getBean(FriendService.class);

                CompletableFuture<Boolean> future = friendService.addFriend(roleId1, roleId2);

                try {
                    log.info("添加结果={}", future.get());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            });
        }
    }

}

/*
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-4-thread-1] com.example.testsb.TestSbApplication     : roleId=2,friends=[1]
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [ool-10-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-3-thread-1] com.example.testsb.TestSbApplication     : roleId=1,friends=[2]
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-6-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-9-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-5-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-2-thread-1] com.example.testsb.TestSbApplication     : 添加结果=true
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-8-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [pool-7-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.695+08:00  INFO 41980 --- [pool-4-thread-1] com.example.testsb.TestSbApplication     : roleId=2,friends=[1]
2024-03-11T11:21:28.695+08:00  INFO 41980 --- [pool-3-thread-1] com.example.testsb.TestSbApplication     : roleId=1,friends=[2]
2024-03-11T11:21:28.694+08:00  INFO 41980 --- [ool-11-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.695+08:00  INFO 41980 --- [pool-3-thread-1] com.example.testsb.TestSbApplication     : roleId=1,friends=[2]
2024-03-11T11:21:28.695+08:00  INFO 41980 --- [pool-4-thread-1] com.example.testsb.TestSbApplication     : roleId=2,friends=[1]
2024-03-11T11:21:28.696+08:00  INFO 41980 --- [pool-3-thread-1] com.example.testsb.TestSbApplication     : roleId=1,friends=[2]
2024-03-11T11:21:28.696+08:00  INFO 41980 --- [pool-4-thread-1] com.example.testsb.TestSbApplication     : roleId=2,friends=[1]
2024-03-11T11:21:28.696+08:00  INFO 41980 --- [pool-3-thread-1] com.example.testsb.TestSbApplication     : roleId=1,friends=[2]
2024-03-11T11:21:28.696+08:00  INFO 41980 --- [pool-4-thread-1] com.example.testsb.TestSbApplication     : roleId=2,friends=[1]
2024-03-11T11:21:28.696+08:00  INFO 41980 --- [pool-3-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
2024-03-11T11:21:28.696+08:00  INFO 41980 --- [pool-4-thread-1] com.example.testsb.TestSbApplication     : 添加结果=false
 */

----------------------

》》》例子1:

ThreadManager.java

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadManager {
    // 逻辑线程
    private static final AtomicInteger logicExecutorIndexObj = new AtomicInteger(0);
    private static Executor[] logicExecutors;

    // 好友线程
    private static final Executor friendExecutor = Executors.newSingleThreadExecutor(r -> {
        Thread thread = new Thread(r);
        thread.setName("FriendThread");
        return thread;
    });

    public static void init() {
        logicExecutors = new Executor[Runtime.getRuntime().availableProcessors()];

        for (int i = 0; i < logicExecutors.length; i++) {
            logicExecutors[i] = Executors.newSingleThreadExecutor(r -> {
                Thread thread = new Thread(r);
                thread.setName("LogicThread-" + logicExecutorIndexObj.getAndIncrement());
                return thread;
            });
        }
    }

    /**
     * 选择一个玩家线程执行器
     *
     * @param hashObj
     * @return
     */
    public static Executor selectLogicThread(Object hashObj) {
        int hashCode = hashObj.hashCode();
        return logicExecutors[Math.abs(hashCode % logicExecutors.length)];
    }

    /**
     * 选择好友线程执行器
     *
     * @return
     */
    public static Executor selectFriendThread() {
        return friendExecutor;
    }
}

FriendManager.java

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;

public class FriendManager {
    // 由于需要支持在别的线程查询,这里采用线程安全的List
    private static final CopyOnWriteArrayList<Integer> fiendList1 = new CopyOnWriteArrayList<>();
    private static final CopyOnWriteArrayList<Integer> fiendList2 = new CopyOnWriteArrayList<>();

    /**
     * 查询好友列表
     * 可以在玩家线程执行,但是不可修改数据
     * 在好友线程不仅可以查询,还可以修改
     *
     * @param roleId
     * @return
     */
    public static CopyOnWriteArrayList<Integer> queryFiendList(int roleId) {
        if (roleId == 1) {
            return fiendList1;
        }

        if (roleId == 2) {
            return fiendList2;
        }

        throw new RuntimeException("错误的roleId=" + roleId);
    }

    /**
     * 添加操作必须在好友线程单线程执行
     *
     * @param roleId1
     * @param roleId2
     */
    public static void tryAddFriend(int roleId1, int roleId2) {
        CompletableFuture.runAsync(() -> {
            try {
                // 直接在好友线程进行查询和逻辑操作
                CopyOnWriteArrayList<Integer> friend1List = queryFiendList(roleId1);
                if (friend1List.contains(roleId2)) {
                    System.out.println(Thread.currentThread().getName() + " " + roleId1 + "已经有好友" + roleId2);
                    return;
                }

                CopyOnWriteArrayList<Integer> friend2List = queryFiendList(roleId2);
                if (friend2List.contains(roleId1)) {
                    System.out.println(Thread.currentThread().getName() + " " + roleId2 + "已经有好友" + roleId1);
                    return;
                }

                // 在好友线程进行修改,保证单线程
                friend1List.add(roleId2);
                System.out.println(Thread.currentThread().getName() + " 添加好友成功! " + roleId1 + " 好友列表" + friend1List);

                friend2List.add(roleId1);
                System.out.println(Thread.currentThread().getName() + " 添加好友成功! " + roleId2 + " 好友列表" + friend2List);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, ThreadManager.selectFriendThread());
    }
}

Main.java

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;

public class Main {

    public static void main(String[] args) {
        ThreadManager.init();

        final int roleId1 = 1;
        final int roleId2 = 2;

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                FriendManager.tryAddFriend(roleId1, roleId2);
            }).start();

            new Thread(() -> {
                FriendManager.tryAddFriend(roleId2, roleId1);
            }).start();

            new Thread(() -> {
                int roleId = ThreadLocalRandom.current().nextInt(1, 3);

                CompletableFuture.runAsync(() -> {
                    CopyOnWriteArrayList<Integer> friends = FriendManager.queryFiendList(roleId);
                    System.out.println(Thread.currentThread().getName() + " " + roleId + " 好友列表是:" + friends);
                }, ThreadManager.selectLogicThread(roleId));
            }).start();
        }
    }
}

/*
LogicThread-1 2 好友列表是:[]
LogicThread-0 1 好友列表是:[2]
FriendThread 添加好友成功! 1 好友列表[2]
LogicThread-0 1 好友列表是:[2]
LogicThread-1 2 好友列表是:[1]
LogicThread-0 1 好友列表是:[2]
LogicThread-1 2 好友列表是:[1]
LogicThread-0 1 好友列表是:[2]
LogicThread-1 2 好友列表是:[1]
LogicThread-1 2 好友列表是:[1]
LogicThread-1 2 好友列表是:[1]
FriendThread 添加好友成功! 2 好友列表[1]
FriendThread 2已经有好友1
FriendThread 1已经有好友2
FriendThread 1已经有好友2
FriendThread 2已经有好友1
FriendThread 1已经有好友2
FriendThread 2已经有好友1
FriendThread 2已经有好友1
FriendThread 2已经有好友1
FriendThread 1已经有好友2
FriendThread 2已经有好友1
FriendThread 1已经有好友2
FriendThread 1已经有好友2
FriendThread 2已经有好友1
FriendThread 2已经有好友1
FriendThread 2已经有好友1
FriendThread 1已经有好友2
FriendThread 1已经有好友2
FriendThread 1已经有好友2
FriendThread 2已经有好友1
 */

总结:

1.其实就是保证:在单线程写就完事,其它线程也想读的话,尽量使用juc中的包,防止一个线程写时,其它线程读也报错。

2.这样子的话,使用mongodb就非常容易了(实际上zfoo中也是这么用的),只需要保证: 玩家之间的交互使用线程安全的容器即可(写到这里我感觉我多线程的功力更加深入了一层了,万变不离其宗,我们接下来写匹配业务什么的就非常简单了!!!匹配业务只不过也是玩家的查询的话,在自己线程查询就行,这样子也是同步写法,匹配逻辑的话,在匹配线程进行即可,非常的简单啦!!!)。

》》》例子2:

需求:

1.我们希望玩家的业务在玩家线程执行,无需回调,因此是多线程处理。

2.匹配线程负责匹配逻辑,是单独一个线程。

3.排行榜线程负责玩家的上榜等。

4.从排行榜线程获取到排行榜列表后,需要给玩家发奖修改玩家数据,因此涉及到排行榜线程和玩家线程的交互。

5.房间线程也希望有多个,这样子各个房间之间业务无交互,进行并行执行。

ThreadManager.java // 负责所有线程的创建

package org.example.testLogicAndRank;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadManager {
    /*** 逻辑专用线程*/
    public static ExecutorService[] logicThreadArr;

    /*** 房间专用线程*/
    public static ExecutorService[] roomThreadArr;

    /*** 排行榜专用线程*/
    public static ExecutorService rankExecutorService = Executors.newSingleThreadExecutor(r -> {
        Thread t = new Thread(r);
        t.setName("RankThread");
        return t;
    });

    /*** 匹配专用线程*/
    public static ExecutorService matchExecutorService = Executors.newSingleThreadExecutor(r -> {
        Thread t = new Thread(r);
        t.setName("RankThread");
        return t;
    });

    public static void init() {
        // 逻辑线程池
        logicThreadArr = new ExecutorService[Runtime.getRuntime().availableProcessors()];
        for (int i = 0; i < logicThreadArr.length; i++) {
            int finalI = i;
            logicThreadArr[i] = Executors.newSingleThreadExecutor(r -> {
                Thread t = new Thread(r);
                t.setName("LogicThread" + finalI);
                return t;
            });
        }

        // 房间线程池
        roomThreadArr = new ExecutorService[Runtime.getRuntime().availableProcessors()];
        for (int i = 0; i < roomThreadArr.length; i++) {
            int finalI = i;
            roomThreadArr[i] = Executors.newSingleThreadExecutor(r -> {
                Thread t = new Thread(r);
                t.setName("RoomThread" + finalI);
                return t;
            });
        }
    }
}

LogicThreadManager.java //逻辑线程池

package org.example.testLogicAndRank;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;

public class LogicThreadManager {
    /**
     * 从其他线程执行一个任务,然后将结果提交到逻辑线程
     *
     * @param completableFuture
     * @param consumer
     * @param <T>
     */
    public static <T> void executeInLogicThread(CompletableFuture<T> completableFuture, Consumer<T> consumer, Object hashObj) {
        ExecutorService executorService = ThreadManager.logicThreadArr[Math.abs(hashObj.hashCode()) % ThreadManager.logicThreadArr.length];

        completableFuture.thenAcceptAsync(consumer, executorService);
    }
}

RankThreadManager.java

package org.example.testLogicAndRank;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

@Slf4j
public class RankThreadManager {
    public static CompletableFuture<List<Integer>> getRankList() {
        return submitInRankThread(() -> {
            // 查询数据库
            log.info("查询排行榜");
            return Lists.newArrayList(1, 2, 3, 4, 5);
        });
    }

    /**
     * 在排行榜线程执行某个操作,有返回值
     *
     * @param callable
     * @param <T>
     * @return
     */
    public static <T> CompletableFuture<T> submitInRankThread(Callable<T> callable) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return callable.call();
            } catch (Exception e) {
                log.error("", e);
            }
            return null;
        }, ThreadManager.rankExecutorService);
    }

    /**
     * 在排行榜线程执行某个操作,无返回值
     */
    public static void executeInRankThread(Runnable runnable) {
        ThreadManager.rankExecutorService.submit(runnable);
    }
}

Main.java

package org.example.testLogicAndRank;

import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.CompletableFuture;

@Slf4j
public class Main {
    public static void main(String[] args) {
        ThreadManager.init();

        // 排行榜
        CompletableFuture<List<Integer>> rankListFuture = RankThreadManager.getRankList();

        // 假设是给玩家1和2发奖
        LogicThreadManager.executeInLogicThread(rankListFuture, (rankList) -> {
            log.info("拿到排行榜数据发奖{}", rankList);
        }, 1);

        LogicThreadManager.executeInLogicThread(rankListFuture, (rankList) -> {
            log.info("拿到排行榜数据发奖{}", rankList);
        }, 2);
    }
}

/*
17:16:39.314 [RankThread] INFO org.example.testLogicAndRank.RankManager - 查询排行榜
17:16:39.343 [LogicThread2] INFO org.example.testLogicAndRank.Main - 拿到排行榜数据发奖[1, 2, 3, 4, 5]
17:16:39.343 [LogicThread1] INFO org.example.testLogicAndRank.Main - 拿到排行榜数据发奖[1, 2, 3, 4, 5]
 */

总结:

可以看出来,我们不再需要什么Promise模式了,有了CompletableFuture后,业务线程的编排和交换数据变得非常容易了!

》》》例子3:

import java.util.List;
import java.util.concurrent.CompletableFuture;

public class Test {

    /**
     * 在异步线程执行任务,执行完毕后,带返回值其它线程可以获取到
     *
     * @return
     */
    public static CompletableFuture<List<Integer>> getValueAsync(int param1) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " " + param1 + " getValueAsync");
            return List.of(param1 * 10);
        }, ThreadManager.selectLogicThread(param1));
    }

    /**
     * 在指定的线程执行业务,不需要返回值
     *
     * @param param1
     */
    public static void execAsync(int param1) {
        CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " " + param1 + " execAsync");
        }, ThreadManager.selectLogicThread(1));
    }

    public static void main(String[] args) {
        ThreadManager.init();

        // 在指定逻辑线程获取值,并且回到好友线程
        getValueAsync(2)
                .whenCompleteAsync((v, e) -> {
                    System.out.println(Thread.currentThread().getName() + " " + v);
                }, ThreadManager.selectFriendThread());

        // 在指定逻辑线程执行业务
        execAsync(1);
    }
}

/*
LogicThread-0 2 getValueAsync
LogicThread-1 1 execAsync
FriendThread [20]
 */

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值