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

时间:2016-03-10 06:11:19

标签: java multithreading

我正在编写一款有趣的游戏,其中玩家可以跳过并射击激光的精灵。它最多可以有三名玩家。我的班级Sprite对于所有三个玩家都是相同的,只是根据玩家#的结构给出了不同的控制布局。 Sprite使用KeyListener来运行。

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

我想到的是使用"帮助"类implements Runnable然后在其run()方法中创建新的Sprite对象。 然而,这似乎是一种混乱的方法。有没有办法让我在一个全新的线程(Sprite上创建所有新的KeyListener个对象,并且所有这些都包含在这个帖子中?

代码:

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

    }

}

当前解决方案:

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

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

}

...

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();
    }
}

修改

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

1 个答案:

答案 0 :(得分: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; // (;
            ...
        }
    }
}

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

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

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

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);
            ...
        }
    }
}

在你为每个精灵切掉无用的线程之后:

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
    ...
}

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

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

因此,无数个精灵和/或其他游戏对象,每个都由它自己的线程支持,这只是你可以想象的最差设计。它可以真正降低你的滴答率。

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

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

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

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

Lag sux,我的朋友。