You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java多线程:通过方法引用分离tick与render的线程执行逻辑

问题描述

现有Java代码中,Looper类的run方法在同一线程中依次调用State类的tick()和render()抽象方法(二者均无参无返回值,由子类实现具体逻辑)。需求是创建两个独立线程分别执行tick和render方法,且不重复编写Looper类。已知C++支持函数指针,但Java不支持,尝试过定义无参无返回的函数式接口但不知如何运用。

原代码

Looper类

public class Looper implements Runnable {

    private final int SECOND = 1000000000;

    private final ResourceBundle res = ResourceBundle.getBundle("game_properties");
    private final int FPS = Integer.parseInt(res.getString("FPS"));
    private final State menu;

    public Looper(State menu) {
        this.menu = menu;
    }
    @Override
    public void run() {
        long quanta = SECOND/FPS;
        long delta = 0;
        long last = System.nanoTime();

        while (true) {
            long now = System.nanoTime();
            delta += now - last;
            last = now;
            if (delta > quanta) {
                delta = 0;
                try {
                    State.getState().tick(); //期望在一个Looper线程执行
                    State.getState().render(); //期望在另一个Looper线程执行
                } catch(RuntimeException e) {
                    System.out.println("Exception in Looper, Switched to Menu State");
                    State.setState(menu);
                }
            }
        }
    }
}

Launcher类

public class Launcher {

    public static void main(String[] args) throws IOException {

        State menu = new MenuState();
        State.setState(menu);
        Thread loopThread = new Thread(new Looper(menu));

        State.getState().init();
        loopThread.start();

    }
    
}
解决方案

核心思路是用函数式接口封装tick和render的执行逻辑,让Looper类接收不同的逻辑作为参数,从而复用同一个Looper创建不同职责的线程。

步骤1:定义无参无返回的函数式接口

可以自定义语义更清晰的接口,也可以直接用Java自带的Runnable,这里自定义一个贴合场景的:

@FunctionalInterface
public interface StateAction {
    void execute();
}

步骤2:改造Looper类

修改构造方法,新增StateAction参数接收要执行的逻辑,同时保留menu用于异常处理:

public class Looper implements Runnable {

    private final int SECOND = 1000000000;

    private final ResourceBundle res = ResourceBundle.getBundle("game_properties");
    private final int FPS = Integer.parseInt(res.getString("FPS"));
    private final State menu;
    private final StateAction action; // 新增:要执行的逻辑

    // 改造构造方法,传入要执行的动作
    public Looper(State menu, StateAction action) {
        this.menu = menu;
        this.action = action;
    }

    @Override
    public void run() {
        long quanta = SECOND/FPS;
        long delta = 0;
        long last = System.nanoTime();

        while (true) {
            long now = System.nanoTime();
            delta += now - last;
            last = now;
            if (delta > quanta) {
                delta = 0;
                try {
                    action.execute(); // 执行传入的逻辑,不再硬编码tick或render
                } catch(RuntimeException e) {
                    System.out.println("Exception in Looper, Switched to Menu State");
                    State.setState(menu);
                }
            }
        }
    }
}

步骤3:修改Launcher启动两个线程

分别创建执行tick和render的Looper实例,启动独立线程:

public class Launcher {

    public static void main(String[] args) throws IOException {

        State menu = new MenuState();
        State.setState(menu);
        State.getState().init();

        // 创建执行tick的线程
        Thread tickThread = new Thread(new Looper(menu, () -> State.getState().tick()));
        // 创建执行render的线程
        Thread renderThread = new Thread(new Looper(menu, () -> State.getState().render()));

        tickThread.start();
        renderThread.start();
    }
    
}

关键说明

  • 用Lambda表达式实现StateAction接口,代码更简洁(也可以用匿名内部类替代)
  • 同一个Looper类可复用,只需传入不同的StateAction逻辑,即可实现不同线程的职责划分
  • 注意线程安全:如果tick和render操作共享State对象的状态,需确保操作是线程安全的(比如使用synchronized或并发容器)

内容的提问来源于stack exchange,提问作者zjschrage

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 09:37:37