'Creating Game using multithreading, but have problems with smoothness of background movement in Java Swing

I'm working on a project which is about creating a game. I was implementing threads to move my background but had some problems with the smoothness of background movement. I also have a bird component, but it is supposed to be used with keybindings. Both of these components are drawn by using the paintComponent() method.

The problem is when I start the game, the background starts moving, but after 1-2 seconds, it starts lagging, but when I use keys to move my bird component right and left, the background movement becomes smooth again, and when I don't press any key, it starts lagging again. I also observed that when I hover the mouse over the window, it also starts moving smoothly. I couldn't realize the problem. Tried to use alternatives instead of original ones, like using labels instead of paintComponent() and using timer. instead of thread, but nothing changes.

I also searched about this problem a lot, and found some options like PC performance and so on. But I don't think that I have a problem with that. Is there a way to solve this problem? Thanks beforehand! Here is the code:

FlyingChicken.java

This is the main class to call other classes

import java.io.*;
import java.time.*;
import java.lang.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;

public class FlyingChicken extends JFrame implements ActionListener{

    JButton startButton, firstLoginButton, firstRegisterButton, firstQuitButton;
    boolean gameStarted = false, gameOver = false, gamePaused = false;
    RegisterPage registerPage;
    LoginPage loginPage;
    GamePage gamePage;


    public FlyingChicken(){

        //FirstRegisterButton
        firstRegisterButton = new JButton("Register Account");
        firstRegisterButton.setBounds(360,445,200,50);
        firstRegisterButton.setFocusable(false);
        firstRegisterButton.addActionListener(this);
        firstRegisterButton.setBackground(Color.green);
        firstRegisterButton.setForeground(Color.white);

        //FirstLoginButton
        firstLoginButton = new JButton("Login Account");
        firstLoginButton.setBounds(360,520,200,50);
        firstLoginButton.setFocusable(false);
        firstLoginButton.addActionListener(this);
        firstLoginButton.setBackground(Color.green);
        firstLoginButton.setForeground(Color.white);

        //FirstQuitButton
        firstQuitButton = new JButton("Quit the Game");
        firstQuitButton.setBounds(385,595,150,50);
        firstQuitButton.setFocusable(false);
        firstQuitButton.addActionListener(this);
        firstQuitButton.setBackground(Color.green);
        firstQuitButton.setForeground(Color.white);

        //StartButton
        startButton = new JButton("Start Game");
        startButton.setBounds(360,495,200,50);
        startButton.setFocusable(false);
        startButton.addActionListener(this);
        startButton.setVisible(false);
        startButton.setFont(new Font("Arial",Font.BOLD, 20));

        loginPage = new LoginPage();
        registerPage = new RegisterPage();
        gamePage = new GamePage();

        loginPage.loginPageVisibility(false);
        registerPage.registerPageVisibility(false);
        gamePage.gamePageVisibility(false);

        loginPage.backButton.addActionListener(this);
        loginPage.loginButton.addActionListener(this);
        registerPage.registerButton.addActionListener(this);
        registerPage.backButton.addActionListener(this);
        gamePage.quitButton.addActionListener(this);

        //Frame Settings
        Image img = new ImageIcon("Flying_Chicken_Icon.png").getImage();
        this.setIconImage(img);
        this.setSize(920,1080);
        this.setTitle("Flying Chicken");
        this.getContentPane().setBackground(new Color(14, 194, 176));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);
        this.setFocusable(true);
        this.setLayout(null);
        this.setVisible(true);
        this.add(loginPage);this.add(registerPage);
        this.add(gamePage);this.add(startButton);
        this.add(firstQuitButton);this.add(firstRegisterButton);
        this.add(firstLoginButton);

    }

    public void mainPanel(){
        this.getContentPane().setBackground(new Color(14, 194, 176));
        firstRegisterButton.setVisible(true);
        firstQuitButton.setVisible(true);   
        firstLoginButton.setVisible(true);
        loginPage.loginPageVisibility(false);
        registerPage.registerPageVisibility(false);
        loginPage.loginLabel.setVisible(false);
        registerPage.label1.setVisible(false);
        registerPage.label2.setVisible(false);
        registerPage.label3.setVisible(false);
        registerPage.label4.setVisible(false);
        registerPage.label5.setVisible(false);  
    }

    public void loginPanel(){

        loginPage.tpassword.setText("");
        loginPage.tusername.setText("");
        firstRegisterButton.setVisible(false);
        firstQuitButton.setVisible(false);  
        firstLoginButton.setVisible(false);
        loginPage.loginPageVisibility(true);
        registerPage.registerPageVisibility(false);
        
    }

    public void registerPanel(){

        loginPage.loginPageVisibility(false);
        registerPage.registerPageVisibility(true);
        registerPage.tpassword.setText("");
        registerPage.tusername.setText(""); 
        firstRegisterButton.setVisible(false);
        firstQuitButton.setVisible(false);  
        firstLoginButton.setVisible(false);
    }

    public void startPage(){

        this.getContentPane().setBackground(new Color(128,198,209));
        startButton.setVisible(true);
        loginPage.loginPageVisibility(false);
        registerPage.registerPageVisibility(false); 
    }

    public void startGame(){

        startButton.setVisible(false);
        gamePage.gamePageVisibility(true);
        // gamePage.timer.start();
        gameStarted = true;
    }

    public void quitFunction(){
        String[] resp = {"Resume", "Quit"};
        int k = JOptionPane.showOptionDialog(this, "Are you sure you want to quit?","Quit Panel",JOptionPane.YES_NO_OPTION,JOptionPane.INFORMATION_MESSAGE, null, resp, 0);
        if (k==1){
            this.setVisible(false);
            this.dispose();
        }
    }

    @Override
    public void actionPerformed(ActionEvent e){
        if(e.getSource() == startButton){
            startGame();
        }
        if(e.getSource() == gamePage.quitButton){
            // gamePage.timer.stop();
            quitFunction();
        }
        if(e.getSource() == firstQuitButton){
            quitFunction();
        }
        if(e.getSource() == firstRegisterButton){
            registerPanel();
        }
        if(e.getSource() == firstLoginButton){
            loginPanel();
        }
        if(e.getSource() == registerPage.registerButton){
            if(registerPage.registerValidate() == false){
                registerPage.registerData();
                loginPanel();
            }
        }
        if(e.getSource() == loginPage.loginButton){
            if(loginPage.loginValidate() == false)
                startPage();
        }
        if(e.getSource() == loginPage.backButton || e.getSource() == registerPage.backButton){
            mainPanel();
        }
    }

    public static void main(String[] args){

        FlyingChicken game = new FlyingChicken();
    }
}

GamePage.java

This is the class that I'm supposed to create animations and game

import java.io.*;
import java.time.*;
import java.lang.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;

public class GamePage extends JPanel implements ActionListener, Runnable{

    JLabel scoreBoard;
    Image backgroundImage, birdImage, catImage;
    JButton quitButton;
    int birdX, birdY, xpos=0, ypos=0, score = 0;
    Timer timer;
    Thread animator;
    Action leftAction, rightAction;

    public GamePage(){

        //GamePage Panel Settings
        this.setSize(920,1080);
        this.setLocation(xpos,ypos);
        this.setLayout(null);
        this.setFocusable(true);
        this.setVisible(true);

        backgroundImage = new ImageIcon("background.jpg").getImage();
        birdImage = new ImageIcon("bird.png").getImage();
        birdX = 264;birdY = 230;



        //Scoreboard
        scoreBoard = new JLabel("Score: "+ score);
        scoreBoard.setOpaque(false);
        scoreBoard.setForeground(new Color(33,30,31));
        scoreBoard.setFont(new Font("Arial", Font.BOLD, 25));
        scoreBoard.setBounds(0,0,200,30);
        scoreBoard.setVisible(true);

        //QuitButton
        quitButton = new JButton("Quit");
        quitButton.setBounds(820,0,100,50);
        quitButton.setFocusable(false);
        quitButton.setVisible(true);

        // timer = new Timer(10,this);
        createKeyBindings(this);

        this.add(quitButton);
        this.add(scoreBoard);
    }

    public void addNotify(){
        super.addNotify();
        animator = new Thread(this);
        animator.start();
    }

    @Override
    public void run(){

        while(true){
            try{
                ypos = ypos-1;
                repaint();
                Thread.sleep(25);
            } catch(InterruptedException e){System.out.println("hey");}         
        }
    }

    //The function to draw background
    @Override
    public void paintComponent(Graphics g){

        super.paintComponent(g);
        g.drawImage(backgroundImage, xpos, ypos, null);
        g.drawImage(birdImage,birdX,birdY,null);
    }

    @Override
    public void actionPerformed(ActionEvent e){
        
        // scoreBoard.setText("Score: "+score);
        repaint();
    }

    //The function to create keybindings
    public void createKeyBindings(JPanel p) {

        InputMap im = p.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
        ActionMap am = p.getActionMap();
        leftAction = new LeftAction();
        rightAction = new RightAction();
        im.put(KeyStroke.getKeyStroke("LEFT"), "left");
        im.put(KeyStroke.getKeyStroke("RIGHT"), "right");
        am.put("left", leftAction);
        am.put("right", rightAction);
    }

    public class LeftAction extends AbstractAction{
        @Override
        public void actionPerformed(ActionEvent e){
            if(birdX>0)
                birdX = birdX-10;
            repaint();
        }
    }

    public class RightAction extends AbstractAction{
        @Override
        public void actionPerformed(ActionEvent e){
            if(birdX+530<1080)
                birdX = birdX+10;
            repaint();
        }
    }

    public void gamePageVisibility(boolean x){
        this.setVisible(x);
    }

}

EDIT: I found the actual reason my application was lagging. The problem was mainly related with Linux OS graphics and solved it by adding Toolkit.getDefaultToolkit().sync() to my loop.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source