Java | Как я могу заставить один из потоков спать, пока другие еще работают? - PullRequest
0 голосов
/ 01 мая 2020

В настоящее время я работаю над приложением, которое использует функцию рисования для анимации «прыгающих изображений» в JPanel. Чтобы выполнить это, я должен был научиться использовать потоки. Когда я использовал их в своем коде, кто-то рекомендовал, чтобы вместо непосредственного использования потоков я мог использовать такие вещи, как инфраструктура Executor и ExecutorService.

Сейчас моя проблема заключается в том, что при добавлении новых изображений мне нужно следить, чтобы они не создавались внутри друг друга. Когда программа обнаруживает, что они будут пересекаться, она должна подождать некоторое время, пока все остальные потоки продолжают работать, поэтому изображения по-прежнему перемещаются и рисуются в текущих позициях. Однако происходит то, что когда я засыпаю в один из потоков, ожидая, пока не освободится место, вся программа зависает. Единственное, что кажется работающим - это функция перемещения изображения.

Код в Github Gist

Вот код:

Это класс BouncingImages


/* NOTE: requires MyImage.java */

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


import javax.swing.*;


public class BouncingImages extends JFrame implements ActionListener {

    public static void main(String[] args) {
        new BouncingImages();
    }

    static boolean imagesLoaded = true;
    JPanel resetPanel   = new JPanel();
    JPanel runningPanel = new JPanel();
    JPanel pausedPanel  = new JPanel();
    JPanel btnPanel     = new JPanel();
    public static AnimationPanel animationPanel;
    ArrayList <MyImage> imageList = new ArrayList <MyImage>();
    private volatile boolean stopRequested = false; //maybe should use AtomicBoolean
    boolean isRunning = false;
    boolean isReset = true;
    ExecutorService service = Executors.newCachedThreadPool();
    Future f;

    //here is the part of the code responsible for creating a JFrame
    BouncingImages() {

        //set up button panel
        JButton btnStart  = new JButton("Start");
        JButton btnResume = new JButton("Resume");
        JButton btnAdd    = new JButton("Add");
        JButton btnAdd10  = new JButton("Add 10");
        JButton btnStop   = new JButton("Stop");
        JButton btnReset  = new JButton("Reset");
        JButton btnExit   = new JButton("Exit");

        btnStart.addActionListener(this);
        btnResume.addActionListener(this);
        btnAdd.addActionListener(this);
        btnAdd10.addActionListener(this);
        btnStop.addActionListener(this);
        btnReset.addActionListener(this);
        btnExit.addActionListener(this);

        resetPanel.add(btnStart);

        runningPanel.add(btnAdd);
        runningPanel.add(btnAdd10);
        runningPanel.add(btnStop);

        pausedPanel.add(btnResume);
        pausedPanel.add(btnReset);

        animationPanel = new AnimationPanel();

        resetButtons();
        this.add(btnPanel, BorderLayout.SOUTH);
        this.add(animationPanel);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack(); //since the JPanel is controlling the size, we need pack() here.
        this.setLocationRelativeTo(null);    //after pack();
        this.setVisible(true);
    }

    //I use different JPanels with designated buttons to make them display different buttons when the program is running, paused or completely restarted.
    public void resetButtons() {
        btnPanel.updateUI();
        if (isReset) {
            btnPanel.removeAll();
            btnPanel.add(resetPanel, BorderLayout.SOUTH);
        } else {
            if (isRunning) {
                btnPanel.removeAll();
                btnPanel.add(runningPanel, BorderLayout.SOUTH);
            }
            if (!isRunning) {
                btnPanel.removeAll();
                btnPanel.add(pausedPanel, BorderLayout.SOUTH);
            }
        }
    }

    //ActionListener for Buttons
    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("Start")) {
            startAnimation();
            isRunning = true;
            isReset = false;
            resetButtons();
        }
        if (e.getActionCommand().equals("Resume")) {
            startAnimation();
            isRunning = true;
            resetButtons();
        }
        if (e.getActionCommand().equals("Add")) {
            addImage();

        }
        if (e.getActionCommand().equals("Add 10")) {
            for(int i = 0; i <10; i++) {
                addImage();
                startAnimation();
            }

        }
        if (e.getActionCommand().equals("Stop")) {
            pauseAnimation();
            isRunning = false;
            resetButtons();
        }
        if (e.getActionCommand().equals("Reset")) {
            imageList.clear();
            repaint();
            isReset = true;
            resetButtons();
        }
        if (e.getActionCommand().equals("Exit")) {
            System.exit(0);
        }

    }
    //This function starts all the animations using the Runnable AnimationThread
    void startAnimation() {
        //this starts the program
        if (f == null) {
            f = service.submit(new AnimationThread());
        }
        //this starts the program after it got paused
        else if (f.isCancelled()) {
            f = service.submit(new AnimationThread());
        }
    }
    //this pauses all the animations
    void pauseAnimation() {
        f.cancel(true);
    }

    //here is the part of the code that I have problems with. I'm not sure how to make the program wait for a spot to be empty while all the other threads are running rather than pausing them all.
    void addImage(){
        int i = 0;
        MyImage image;
        while (true) {
            image= new MyImage("image.png");
            if (checkCollision(image)) {
                if(i > 100){
                    System.out.println("Something went wrong");
                    System.exit(0);
                }
                try {
                    i++;
                    Thread.sleep(50);
                } catch (InterruptedException e) {}
            } else {
                System.out.println("image added");
                break;
            }
        }

        imageList.add(image);
    }

    //this part of the program does all the image moving. It uses the function move image from the MyImage class and a checkCollision function from this class.
    void moveAllImages() {
        for (MyImage image : imageList) {
            image.moveImage(animationPanel);
            image.calculatePoints();
            checkCollision(image);
        }
    }

    //this checks all the collisions with other images. It can be used for checking if a image can be created in some spot and also for all the bouncing callculations
    boolean checkCollision(MyImage currentImage){

        if(imageList.isEmpty()) return false;
        for(MyImage im : imageList){
            if(currentImage == im) continue;

            if(currentImage.intersects(im)){
                if(im.contains(currentImage.ml) || im.contains(currentImage.mr)){
                    currentImage.undoMove();
                    currentImage.vx = -currentImage.vx;
                    return true;
                }
            }

            if(im.contains(currentImage.mt) || im.contains(currentImage.mb)){
                currentImage.undoMove();
                currentImage.vy = - currentImage.vy;
                return true;
            }

            if(currentImage.contains(im.ml) || currentImage.contains(im.mr)){
                currentImage.undoMove();
                currentImage.vx = -currentImage.vx;
                return true;
            }

            if (im.contains(currentImage.tl) || im.contains(currentImage.tr) || im.contains(currentImage.bl) || im.contains(currentImage.br)) {
                currentImage.undoMove();
                currentImage.vx *= -1;
                currentImage.vy *= -1;
                return true;
            }
        }

        return false;
    }





    //This class does drawing graphics and nothing else
    public class AnimationPanel extends JPanel {

        AnimationPanel() {
            this.setBackground(Color.BLACK);
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
            this.setPreferredSize(new Dimension((int) screenSize.getWidth() / 2, (int) screenSize.getHeight() / 2));
        }


        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (MyImage im : imageList) {
                g.drawImage(im.image, im.x, im.y, im.width, im.height, null);
            }
        }
    }

    //this is the Runnable AnimationThread which does all the image moving and repainting.
    private class AnimationThread implements Runnable {
        @Override
        public void run() {
            while (!f.isCancelled()) {
                moveAllImages();
                animationPanel.repaint();

                try {
                    Thread.sleep(5);
                }
                catch (InterruptedException e) {
                    System.out.println(e.getMessage());
                    f.cancel(true);
                }

            }

        }

    }
}

Это класс MyImage

package bouncer;

import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;


//NOTE: This class requires BouncingImages.java

/* This class combines an image with a rectangle
 * which allows for easy movement and collision detection
 */
class MyImage extends Rectangle {
    //this gives it an x,y,width,height

    static int biggestDim = 0;
    BufferedImage image;
    Color color = Color.RED;
    //these are the speeds of movement
    int vx = 1;
    int vy = 1;
    int lastx = -1;
    int lasty = -1;

    Point ctr, tl, tr, bl, br, ml, mr, mt, mb;

    MyImage(String filename) {

        vx = (int) (Math.random() * 5 + 1);
        vy = (int) (Math.random() * 5 + 1);

        //load the image
        try {
            image  = ImageIO.read(new File(filename));
            width  = image.getWidth(null);
            height = image.getHeight(null);
        }
        catch (IOException e) {
            System.out.println("ERROR: image file \"" + filename + "\" not found");
            //e.printStackTrace();
            BouncingImages.imagesLoaded = false;
            width                       = 100 + (int) (Math.random() * 100);
            height                      = width - (int) (Math.random() * 70);
            color                       = Color.getHSBColor((float) Math.random(), 1.0f, 1.0f);    // a quick way to get random colours
            //System.exit(0);
        }

        //update the variable containing the biggest dimension
        if (width > biggestDim) biggestDim = width;
        if (height > biggestDim) biggestDim = height;

        calculatePoints();
    }

    void calculatePoints() {
        //Calculate points
        //corners
        tl = new Point(x, y);
        tr = new Point(x + width, y);
        bl = new Point(x, y + height);
        br = new Point(x + width, y + height);
        //center
        ctr = new Point(x + width / 2, y + height / 2);
        //mid points of sides
        ml = new Point(x, y + height / 2);
        mr = new Point(x + width, y + height / 2);
        mt = new Point(x + width / 2, y);
        mb = new Point(x + width / 2, y + height);
    }

    void moveImage(BouncingImages.AnimationPanel panel) {
        lastx = x;
        lasty = y;
        x += vx;
        y += vy;
        if (x < 0 && vx < 0) {
            x  = 0;
            vx = -vx;
        }
        if (y < 0 && vy < 0) {
            y  = 0;
            vy = -vy;
        }
        if (x + width > panel.getWidth() && vx > 0) {
            x  = panel.getWidth() - width;
            vx = -vx;
        }
        if (y + height > panel.getHeight() && vy > 0) {
            y  = panel.getHeight() - height;
            vy = -vy;
        }
    }

    void undoMove() {
        if (lastx > 0) {
            x = lastx;
            y = lasty;
        }
        lastx = lasty = -1;
    }
}

1 Ответ

1 голос
/ 01 мая 2020

Вы блокируете основной поток в методе addImage в Thread.sleep(50). Вместо этого вы можете убедиться, что вызываете метод addImage асинхронно, например,

if (e.getActionCommand().equals("Add")) {
   CompletableFuture.runAsync(this::addImage);
}
if (e.getActionCommand().equals("Add 10")) {
   CompletableFuture.runAsync(() -> addImages(10));
}

с

  private void addImages(int numberOfImages) {
    for(int i = 0; i < numberOfImages; i++) {
      addImage();
      startAnimation();
    }
  }

Если вы хотите поэкспериментировать с потоками, вы можете подумать о как это сделать "вручную".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...