如何在全新的线程上启动某个类的每个实例?

Ash*_*pta 7 java multithreading

我正在写一个有趣的游戏,玩家可以跳跃并射击激光的精灵.它最多可以有三名玩家.我的班级Sprite对于所有三个玩家来说都是一样的,每个玩家都有不同的控制布局,具体取决于玩家#在构造中给出的.Sprite使用a KeyListener来起作用.

为了让我有多个玩家同时做事(比如拍摄激光或跳跃),我需要让每个创建的Sprite对象都在一个单独的线程上.我知道我可以Spriteimplements Runnable类上使用,但是这只运行Sprite新线程上的方法中的代码.这不起作用,因为run()Sprite和其他类似的东西不会出现在新线程上.

我认为我应该使用"帮助器"类,keyPressed()然后在其implements Runnable方法中创建新的Sprite对象.然而,这似乎是一种混乱的方法.有没有办法让我run()在一个全新的线程上创建所有新对象(Sprites和所有这些都包含在这个线程中)?

码:

public class Sprite() implements KeyListener { //I want this class on a brand new thread
    int x;
    int y;
    int width;
    int height;
    Image spriteImage; 

    //code/methods for stuff

    //key listeners:

    @Override
    public void keyPressed(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyReleased(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }

}
Run Code Online (Sandbox Code Playgroud)

当前解决方案

public class SpriteStarter(/* Sprite class parameters go here */) implements Runnable{

    void run() {
        Sprite s = new Sprite(/*params*/);
    }

}
Run Code Online (Sandbox Code Playgroud)

...

class Linker() {
    public static void main(String args[]) {
        SpriteStarter s1 = new SpriteStarter();
        SpriteStarter s2 = new SpriteStarter();
        Thread t1 = new Thread(s1);
        Thread t2 = new THread(s2);
        t1.start();
        t2.start();
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

好的,经过很多很好的反馈后,对我来说很明显我的游戏应该是一个单线程的东西.我很抱歉没有意识到这一点,我没有做太多的游戏编程,所以这对我来说是个新事物.我的新想法也有一个ArrayList,在Sprite触发时将按下的键添加到列表中.在Sprite类中,我将有一个update()方法,它查看按下的键并相应地更新坐标.然后将通过java.awt.Timer以固定间隔调用Update().这似乎对我有用,但我不确定所以让我知道!再次感谢大家.此外,我仍然会回答原始问题的答案(如何在新线程上启动类的每个实例),因为它可能对将来的程序有所帮​​助.

dco*_*cow 10

首先让我们直截了当:对象不会在线程上运行.真的,他们不会做任何事情.它们位于内存中并等待某个线程执行其方法.这就是为什么你可以有竞争条件.两个线程可能会一次尝试访问同一个内存(可能在同一个对象上).在你的问题上.


几声呼吸,想想设计.你的输入不是多线程的(至少我猜是这样).事件从操作系统上的某个设备逐个进入您的应用程序(或者,基于您的注释,来自框架抽象,例如窗口面板).通常,更新精灵只涉及琐碎的数学.这可以在为您提供事件的线程上在线完成.

此外,对于每个新事件(如果您想要执行您所描述的操作),您可能会产生更多的开销,而不是简单地执行内联计算.最重要的是,如果您正在处理一个事件并且有一个新事件进入,那么会发生什么?您需要阻止向每个线程提交事件(使线程无效)在线程本地队列中排队事件.

但是..对于娱乐..让我们说更新每个精灵有可能需要很长时间(这很愚蠢,你的游戏将无法播放)......

想为每个精灵一个线程.在每个线程上,您需要一个消息队列.当您启动每个线程时,您将阻止消息队列,直到消息到达.当消息到达时,线程将其从队列中弹出并处理它.您需要在消息中对事件进行编码.消息需要通过值传递到队列中.为简单起见,消息和事件可以是同一个类.

只有一个事件监听器并让侦听器将相应的事件分派给相关的精灵是最容易的.但是如果你想让每个sprite都监听它自己的事件,你只需将它们添加到队列中,以便线程处理sprite本身内的sprite事件.

package sexy.multithreaded.sprites;

public class GameDriver implements EventListener {
    final EventDispatcher dispatcher;
    final Framework framework;
    final List<Sprite> sprites;

    GameDriver(Framework framework) {
        framework.addEventListener(self);
        self.framework = framework;
        sprites = new ArrayList<>();
        dispatcher = new EventDispatcher(sprites);
    }

    public static void main(String[] args) {
        // register for events form your framework
        Framework f = new Framework(); // or window or whatever
        new GameDriver(f).startGame(Integer.parseInt(args[0]));
    }

    void startGame(int players) {
        // initialize game state
        for (int player = 0; player <= players; player++) {
            Sprite s = new Sprite(player);
            sprites.add(s);
            s.start();
        }
        // and your event processing thread
        dispatcher.start();

        // loop forever
        framework.processEvents();
    }

    @Override
    void onEvent(Event e) {
        if (e == Events.Quit) {
            dispatcher.interrupt();
        } eles {
            dispatcher.q.put(e);
        }
    }
}

class EventDispatcher extends Thread implements Runnable {
    // setup a queue for events
    final Queue<Event> q;
    final List<Sprite> sprites;

    EventDispatcher(List<Sprite> sprites) {
        super(this, "Event Dispatcher");
        this.sprites = sprites;
        q = new BlockingQueue<>();
    }

    @Override
    void run() {
        while (!interrupted()) {
            Event e = q.take();
            getSpriteForEvent(e).q.put(e);
        }
        for (Sprite s : sprites) {
            s.interrupt();
        }
    }
}

class Sprite extends Thread implements Runnable {
    final int num;
    final Queue<Event> q;

    Sprite(int num) {
        super(this, "Sprite " + num);
        self.num = num;
        q = new BlockingQueue<>();
    }

    @Override
    void run() {
        while (!interrupted()) {
            Event e = q.take();
            handle(e);
        }
    }

    void handle(Event e) {
        // remember we assumed this takes a really long time..
        // but how do I know how to calculate anything?
        switch (e) {
            case Events.UP:
                   // try to do something really long...
                   waitForUpvoteOn("a/35911559/1254812");
               break; // (;
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你有新问题需要解决.你的游戏需要一个时钟.需要将事件分组到时间窗口中,这些时间窗口可能会或可能不会直接与帧相关联.当一个事件进来并且精灵仍在处理旧事件时会发生什么?你会取消旧活动的处理还是会丢帧?您还必须管理队列的大小 - 不能产生比您可以消耗的更多事件.

关键是必须有确定游戏状态的真相来源.裁判..如果你愿意的话.事实证明,在单个线程上处理所有事件通常最简单.想想看,如果每个sprite/thread都有一个ref,那么他们仍然需要同步他们各自的世界观.这有效地序列化了游戏逻辑的处理.

让我们添加计时器和绘图:

class GameDriver ... {
    static final DELTA = 10; // ms
    final Timer timer;
    ...
    GameDriver(...) {
        ...
        timer = new Timer(dispatcher, DELTA);
        dispatcher = new EventDispatcher(sprites, f.canvas(), map);
    }

    void startGame(...) {
        ...
        // and your event processing thread and timer
        dispatcher.start();
        timer.start();   
        ...
    }

    @Override
    void onEvent(Event e) {
        if (e == Events.Quit) {
            timer.stop();
            dispatcher.interrupt();
        } else {
            if (!dispatcher.q.offer(e)) {
                // Oh no! We're getting more events than we can handle.
                // To avoid getting into this situation you can try to:
                //   1. de-dupe/coalesce/buffer events
                //   2. increase your tick interval (decrease frame rate)
                //   3. drop events (I shot you I swear!)
            }
        }
    }
}

class EventDispatcher ... {
    // setup a queue for events
    final Queue<Event> q;
    final Canvas canvas;

    EventDispatcher(List<Sprite> sprites, Canvas c) {
        super(this, "Event Dispatcher");
        q = new BlockingQueue<>();
        canvas = c;
    }

    @Override
    void tick() {
        for (Sprite s : sprites) {
            canvas.push();
            s.draw(canvas);
            canvas.pop();
        }
    }
}

class Sprite ... implements Drawable ... {
    final Bitmap bitmap;
    final Matrix matrix;
    ...
    Sprite(int num) {
        ...
        URL url = Sprite.class.getResource("sprites/player-"+num+".bmp");
        bitmap = new Bitmap(url);
        matrix = new Matrix();
    }

    @Override
    void draw(Canvas c) {
        c.apply(matrix);
        c.paint(bitmap);
    }

    void handle(Event e) {
        switch (e) {
            case Events.Left:
               matrix.translate(GameDriver.DELTA, 0);
               break;
            case Events.Down:
               matrix.translate(0, GameDriver.DELTA);
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在为每个sprite删除无用的线程之后:

class GameDriver ... {
    void startGame(...) {
        // don't need to start() the sprites anymore..
        ...
            // give sprites a starting position
            sprite.matrix.translate(0, player);
    }
}

class EventDispatcher extends Thread implements Runnable {

    final Map<Matrix, Sprite> map;
    ...
    EventDispatcher(...) {
        ...
        map = new HashMap<>();
    }

    ...
    @Override
    void tick() {
        for (Sprite s : sprites) {
            // assuming we gave matrix a map-unique hash function
            checkBounds(s);
            map.put(s.matrix, s);
        }
        // process collisions or otherwise apply game logic
        applyLogic(map);
        map.clear();

        // draw the sprites (or use yet another thread)
        for (Sprite s : sprites) {
            canvas.push();
            s.draw(canvas);
            canvas.pop();
        }
    }

    @Override
    void run() {
        try {
            while (!interrupted()) {
                Event e = q.take();
                getSpriteForEvent(e).handle(e)
            }
        } catch (InterruptedException e) {
        } finally {
            for (Sprite s : sprites) {
                s.interrupt();
            }
        }
    }
    ...
}

class Sprite implements Drawable {
    ...
    // scratch the run method and thread constructor
    ...
}
Run Code Online (Sandbox Code Playgroud)

我不写游戏,所以我可能有些不对劲..


无论如何,有一些外卖.回想一下,您的计算机具有固定数量的核心.任何数量的线程大于核心数将意味着您必须在线程之间切换上下文.暂停一个线程,保存其寄存器和堆栈,并加载新线程.这是您的操作系统的调度程序所做的事情(如果它支持线程,就像大多数一样).

因此,无限数量的精灵和/或其他游戏对象每个都由它自己的线程支持,这只是你能想象到的最糟糕的设计.它真的会降低你的滴答率.

其次,正如我已经提到的那样,为了避免竞争条件(游戏检查sprite的位置,而sprite的线程正在更新它的中间),你必须同步访问sprite数据.如果您无法在一个线程上的一个刻度内计算逻辑,那么您可以探索使用工作队列来执行精灵更新.但每个精灵不是一个线程.

这就是为什么,如评论中所建议的,3个线程是一个很好的球场.一个与操作系统接口.一个来处理游戏逻辑.一个渲染图形.(如果您使用的是Java,则为GC线程留出空间.)

另一种思考方式是你的工作是找到一个最小的窗口,你可以在其中处理输入,解决游戏状态,并为整个游戏发出渲染事件.然后,重复一遍又一遍.窗口越小,游戏越平滑,帧速率越高.


最后,我应该提一下,你所寻找的模型确实出现在现实世界的游戏设计中,但出于其他原因.想象一下拥有许多客户端和服务器的多人游戏(每个都有效地代表一个新的执行线程).每个客户端将处理自己的输入事件,批量处理,将其转换为游戏事件,并将游戏事件提供给服务器.网络成为您的序列化层.服务器将淹没事件,解决游戏状态,并将回复发送回客户端.客户端将接受回复,更新其本地状态并进行呈现.但是服务器肯定不会为慢速客户端等待几帧.

Lag sux,我的朋友.

  • 只是好奇 - 你从哪里获得灵感来写出这么好又长的答案?经常在2段文字后放弃:( (2认同)