简体   繁体   English

Java thread.sleep(1)睡眠时间超过1毫秒

[英]Java thread.sleep(1) sleeping longer than 1 ms

I'm trying to develop a 2D game using Java. 我正在尝试使用Java开发2D游戏。 So far I've managed to set up the game to use the full screen exclusive mode and to do active rendering in a custom thread. 到目前为止,我已设法将游戏设置为使用全屏独占模式并在自定义线程中进行主动渲染。 The game loop I've decided to use is of the type fixed time step variable rendering. 我决定使用的游戏循环是固定时间步变量渲染类型。 This type of game loop is supposed to render as fast as possible as the device can handle, which I'm not entirely happy about. 这种类型的游戏循环应该尽可能快地渲染,因为设备可以处理,我并不完全满意。 So I'm trying to limit the frame rate using Thread.sleep() . 所以我试图使用Thread.sleep()来限制帧速率。

If I turn off all the rendering, and simply update the game in the game loop, Thread.sleep(1) sleeps successfully about 1 ms . 如果我关闭所有渲染,只需在游戏循环中更新游戏, Thread.sleep(1)成功睡眠约1 ms However, if I turn on the rendering, sometimes Thread.sleep(1) sleeps way longer than 1 ms , like 15 ms . 但是,如果我打开渲染,有时Thread.sleep(1)睡眠时间超过1 ms ,如15 ms I'm turning on/off the rendering by adding/removing the lines: 我通过添加/删除线来打开/关闭渲染:

BufferedImage drawImage = render(Math.min(1d, lag / TIME_PER_UPDATE));
drawToScreen(drawImage);

What is causing the thread to sleep for too long? 导致线程长时间睡眠的原因是什么?

This is my first time posting on these forums, so please, do tell me if I've done something wrong in my post, or if this is a duplicate (I've not managed to find a similar post). 这是我第一次在这些论坛上发帖,所以请告诉我,如果我在帖子中做错了什么,或者这是否重复(我没有找到类似的帖子)。

import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

public class Main implements KeyListener
{

    private static final long serialVersionUID = 1L;
    private boolean gameRunning = false;
    private final double UPDATE_RATE = 60;
    private final double TIME_PER_UPDATE = 1000000000 / UPDATE_RATE;
    private final int MAX_UPDATES_BEFORE_RENDERING = 5;
    private final int TARGET_FPS = 60;
    private int windowWidth;
    private int windowHeight;
    private GraphicsDevice graphicsDevice;
    private DisplayMode defaultDisplayMode;
    private Frame frame;
    private BufferStrategy bufferStrategy;
    private Player player;

    public Main()
    {
        GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
        this.graphicsDevice = screenDevices[0];

        // This is later used to restore the original display mode when closing
        // the game
        defaultDisplayMode = this.graphicsDevice.getDisplayMode();

        frame = new Frame("GameTest");

        frame.setIgnoreRepaint(true);
        frame.setResizable(false);
        frame.setUndecorated(true);

        // Ensure that the user device supports full screen exclusive mode
        if (this.graphicsDevice.isFullScreenSupported())
        {
            graphicsDevice.setFullScreenWindow(frame);
        }

        windowWidth = frame.getWidth();
        windowHeight = frame.getHeight();

        frame.createBufferStrategy(2);

        bufferStrategy = frame.getBufferStrategy();

        // The frame receives keyboard event dispatched on the EDT-thread.
        frame.addKeyListener(this);

        initGame();

        // Starts the gameThread. The updating of the game state and rendering
        GameThread gameThread = new GameThread();
        gameThread.start();

    }

    private void initGame()
    {
        player = new Player(300, 300);
    }

    private class GameThread extends Thread
    {

        @Override
        public void run()
        {
            gameLoop();
        }
    }

    public static void main(String[] Args)
    {
        new Main();
    }

    private void gameLoop()
    {
        gameRunning = true;

        double lastStartTime = System.nanoTime();
        double startTime;
        double elapsedTime = 0;
        double lag = 0;
        double lastRenderTime;
        int updateCount = 0;

        while (gameRunning)
        {
            System.out.println("");
            System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
            System.out.println("New Gameloop");
            System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");

            startTime = System.nanoTime();
            elapsedTime = startTime - lastStartTime;
            lag += elapsedTime;

            updateCount = 0;

            while (lag >= TIME_PER_UPDATE && updateCount < MAX_UPDATES_BEFORE_RENDERING)
            {
                updateGameState();
                lag -= TIME_PER_UPDATE;
                updateCount++;
            }

            if (startTime - lastStartTime > TIME_PER_UPDATE)
            {
                lastStartTime = startTime - TIME_PER_UPDATE;
            }

            BufferedImage drawImage = render(Math.min(1d, lag / TIME_PER_UPDATE));
            drawToScreen(drawImage);
            lastRenderTime = System.nanoTime();

            double currentFPS = 1000000000d / (lastRenderTime - startTime);

            //Sleeps until target FPS is reached
            System.out.println("");
            System.out.println("Before sleeping");
            System.out.println("");
            System.out.println("Current FPS:");
            System.out.println(currentFPS);

            while (currentFPS > TARGET_FPS && (lastRenderTime - startTime) < TIME_PER_UPDATE)
            {

                //Lets the CPU rest
                Thread.yield();

                double beginSleepTime = System.nanoTime();

                try
                {

                    Thread.sleep(1);        

                } catch (Exception e)
                {
                    e.printStackTrace();
                }

                double endSleepTime = System.nanoTime();

                lastRenderTime = System.nanoTime();
                currentFPS = 1000000000d / (lastRenderTime - startTime);

                System.out.println("");
                System.out.println("--------------------------------");
                System.out.println("Sleeping");
                System.out.println("");
                System.out.println("Time slept in ms:");
                System.out.println("");
                System.out.println((endSleepTime - beginSleepTime) / 1000000d);
                System.out.println("");
                System.out.println("current FPS");
                System.out.println("");
                System.out.println(currentFPS);     
            }

            lastStartTime = startTime;
        }   
    }

    private void updateGameState()
    {
        player.update();
    }

    private void drawToScreen(BufferedImage drawImage)
    {
        try
        {
            Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics();

            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            g2d.clearRect(0, 0, windowWidth, windowHeight);
            g2d.setBackground(Color.BLACK);

            g2d.drawImage(drawImage, 0, 0, windowWidth, windowHeight, null);

            g2d.dispose();

            if (!bufferStrategy.contentsLost())
            {
                bufferStrategy.show();
            }

        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private BufferedImage render(double delta)
    {
        BufferedImage drawImage = new BufferedImage(windowWidth, windowHeight, BufferedImage.TYPE_INT_ARGB);
        drawImage.createGraphics();
        Graphics2D g = (Graphics2D) drawImage.getGraphics();

        g.setBackground(Color.WHITE);
        g.clearRect(0, 0, windowWidth, windowHeight);

        //Render player
        g.setColor(Color.BLUE);
        g.fillRect((int) Math.round(player.getLocX() + delta * player.getSpeedX()), (int) Math.round(player.getLocY() + delta * player.getSpeedY()), 64, 64);

        g.dispose();

        return drawImage;
    }

    @Override
    public void keyPressed(KeyEvent keyEvent)
    {
        switch (keyEvent.getKeyCode())
        {

        case KeyEvent.VK_ESCAPE:
            graphicsDevice.setDisplayMode(defaultDisplayMode);
            System.exit(0);
            break;
        case KeyEvent.VK_A:
            player.setSpeedX(-player.getMoveSpeed());
            break;
        case KeyEvent.VK_D:
            player.setSpeedX(player.getMoveSpeed());
            break;
        case KeyEvent.VK_W:
            player.setSpeedY(-player.getMoveSpeed());
            break;
        case KeyEvent.VK_S:
            player.setSpeedY(player.getMoveSpeed());
            break;
        case KeyEvent.VK_SPACE:

            break;
        case KeyEvent.VK_LESS:

            break;
        case KeyEvent.VK_I:

            break;

        }
    }

    @Override
    public void keyReleased(KeyEvent keyEvent)
    {
        switch (keyEvent.getKeyCode())
        {
        case KeyEvent.VK_A:
            player.setSpeedX(0);
            break;
        case KeyEvent.VK_D:
            player.setSpeedX(0);
            break;
        case KeyEvent.VK_W:
            player.setSpeedY(0);
            break;
        case KeyEvent.VK_S:
            player.setSpeedY(0);
            break;
        case KeyEvent.VK_SPACE:

            break;
        case KeyEvent.VK_LESS:

            break;
        case KeyEvent.VK_I:

            break;
        }
    }

    @Override
    public void keyTyped(KeyEvent keyEvent)
    {

    }

    private class Player
    {
        protected double speedX;
        protected double speedY;
        protected double locX;
        protected double locY;
        protected double moveSpeed;

        public Player(int locX, int locY)
        {
            speedX = 0;
            speedY = 0;
            this.locX = locX;
            this.locY = locY;

            moveSpeed = 3d;
        }

        public void update()
        {
            locY +=  speedY;

            locX += speedX;
        }

        public void setSpeedX(double speedX)
        {
            this.speedX = speedX;
        }

        public void setSpeedY(double speedY)
        {
            this.speedY = speedY;
        }

        public double getSpeedX()
        {
            return speedX;
        }


        public double getSpeedY()
        {
            return speedY;
        }

        public double getLocX()
        {
            return locX;
        }

        public double getLocY()
        {
            return locY;
        }

        public double getMoveSpeed()
        {
            return moveSpeed;
        }   
    }
}

The sleep() method in java puts the currently executing thread (in the running state) to sleep for 1 ms. java中的sleep()方法将当前正在执行的线程(处于运行状态)置于休眠状态1 ms。

After 1 ms the thread comes to runnable state ( able to run ), now it depends on the scheduler when to take the thread from the runnable state and execute it (ie, running state). 1 ms后线程进入可运行状态( 能够运行 ),现在它取决于调度程序何时从可运行状态获取线程并执行它(即运行状态)。

For this reason, you can assume that the thread sleeps for a minimum of 1ms before running again. 因此,您可以假设线程在再次运行之前至少休眠1ms。

Below figure describes the different thread states : 下图描述了不同的线程状态 在此输入图像描述

The java docs clearly state (for thread.sleep()) java文档明确说明(对于thread.sleep())

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds plus the specified number of nanoseconds, subject to the precision and accuracy of system timers and schedulers. 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。

You are at the mercy of scheduling and system timing within the system here. 您将受制于系统内的调度和系统时序。 The only way to guarantee the time would be to make sure that the thread blocks on its execution and is prevented from being cycled through but that will create issues elsewhere. 保证时间的唯一方法是确保线程阻塞其执行并阻止循环,但这将在其他地方产生问题。

For what its worth I find it generally bad to sleep threads for fixed amounts of time unless there is an absolute reason to do so things should be triggered by other events not in fixed time intervals. 为了它的价值,我发现在一定时间内睡眠线程通常是不好的,除非有绝对的理由这样做,事情应该由不是固定时间间隔的其他事件触发。

According to javadoc: 根据javadoc:

Thread.sleep() causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. Thread.sleep()使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统定时器和调度程序的精度和准确性。

So, the Thread.sleep(ms) has a low accuracy. 因此, Thread.sleep(ms)的精度较低。

Secondly, note that this method throws a checked exception ThreadInterruptedException . 其次,请注意此方法抛出一个已检查的异常ThreadInterruptedException Which can be triggered by a spurious wakeup . 这可以通过虚假的唤醒来触发。 So even a Thread.sleep(1000) could finish just after a ms. 所以即使一个Thread.sleep(1000)也可以在ms之后完成。

An alternative solution with better precision is LockSupport.parkNanos() . 另一种具有更高精度的解决方案是LockSupport.parkNanos() But also with this method you should watch out for interruptions by other threads. 但是使用这种方法你应该注意其他线程的中断。 PS: There is also a Thread.sleep(ms,nanos) which has the same low accuracy as the Thread.sleep(ms) (the nano seconds are just rounded to ms). PS:还有一个Thread.sleep(ms,nanos)Thread.sleep(ms)具有相同的低精度(纳秒秒仅舍入为ms)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM