简体   繁体   中英

My program keeps creating multiple threads where I only want one background thread created. How can I prevent additional threads from being created?

I'm creating a mobile risk game (client) in Android Studios and a Server in IntelliJ utilizing Java Sockets and ObjectIOStreams sending/receiving Strings to update each clients gamestate when a client finishes their turn.

My current roadblock is when I start my app (currently using a Pixel 2 API R virtual machine) it seems like two Risk games are created, either that or two threads for the Client are being created and the first is sending the correct RiskGame configuration (The one that is displayed in the Apps GUI) and then a second one that is garbage. Here the code for my MainActivity:

package com.example.risk;

import androidx.appcompat.app.AppCompatActivity;

import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

public class MainActivity extends AppCompatActivity {


    RiskGame game; //game instance
    Button next; //next button
    Button Attack; // attack button
    ArrayList<Button> countryButtons; // array list of country buttons
    Random rand;
    TextView army;
    Client client;

        @Override
    protected void onCreate(Bundle savedInstanceState) {
            getSupportActionBar().hide();
            super.onCreate(savedInstanceState); //used by android
            setContentView(R.layout.activity_main);

            army = findViewById(R.id.armyLabel);

            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //force landscape mode

            rand = new Random(); //random object

            countryButtons = new ArrayList<>(); //initialize arrayList

            game = new RiskGame();  //new game instance
            next = findViewById(R.id.nextButt); //next button
            Attack = findViewById(R.id.attackBut); // attack button

            int orentation = getResources().getConfiguration().orientation;
            if (Configuration.ORIENTATION_LANDSCAPE == orentation) {
                for (int i = 0; i < 16; i++) {

                    String name = "c" + (i + 1);
                    Button button = findViewById(getResources().getIdentifier(name, "id", getPackageName()));
                    button.setTag(game.getCountry(i + 1));
                    countryButtons.add(button);

                }

                countryButtonSetUp(countryButtons); //sets up button behavior
                nextButtonSetup();// sets up next button

                Attack.setOnClickListener(v -> { //sets attack button behavior
                    if (game.getPhase() == 2) {
                        game = game.attack();
                    } else if (game.getPhase() == 3) {
                        game = game.move();
                    }
                    updateButtons();
                });

                for (int i = 0; i < 12; i++) {
                    next.performClick();
                }
            } else {

            }

        }

    /**
     * Updates a country button's display features
     */
    public void updateButtons(){
        try {
            for (Button button : this.countryButtons) {
                Country c = (Country) button.getTag();
                button.setTag(game.getCountry(c.getcID()));
                button.setText(c.getcID() + ": " + c.getArmiesHeld()); //sets the text to proper number of armies
                // System.out.println(c.getPlayerNum() + "Player Num"); // delete
                int val = c.getPlayerNum(); //switch statement to set color
                switch (val) {
                    case 1:
                        button.setBackgroundColor(Color.RED);
                        break;
                    case 2:
                        button.setBackgroundColor(Color.WHITE);
                        break;
                    case 3:
                        button.setBackgroundColor(Color.YELLOW);
                        break;
                    case 4:
                        button.setBackgroundColor(Color.BLUE);
                        break;
                    default:
                        break;
                }
                if(game.getPhase() == 1){
                    Attack.setVisibility(View.INVISIBLE);
                }else if(game.getPhase() == 2){
                    Attack.setVisibility(View.VISIBLE);
                    Attack.setText("Attack");
                }else if (game.getPhase() == 3){
                    Attack.setText("Move");
                }

            }
            army.setText(game.getActivePlayer().getPlaceablearmies() + "  ");

        }catch (Exception e){

        }

        //Create the Client object and pass the RiskGame object to it

        }


    /**
     * sets up country button behavior
     * @param buttons
     */
    public void countryButtonSetUp(ArrayList<Button> buttons){

        for(Button button:buttons) { //iterate over each button
            Country c = (Country) button.getTag(); //get country data
            button.setOnClickListener(v ->{ //set on click behavior

                if(game.getPhase() == 1) { //place troop phase of turn
                    if (game.getActivePlayer().getPlayerNum() == c.getPlayerNum()) { //if active player holds country
                       Player active = game.getActivePlayer();
                       if(active.getPlaceablearmies() >0 ) {
                           this.game = game.addArmy(c.getcID());
                           button.setTag(game.getCountry(((Country) button.getTag()).getcID())); //update buttons data
                           active.setPlaceablearmies(active.getPlaceablearmies() -1);
                           updateButtons(); //update display
                       }
                    }
                }else if(game.getPhase() ==2){ // attack phase
                    if(game.getActivePlayer().getPlayerNum() == c.getPlayerNum()){ //player controls country
                        game.setAttackingCount(c); //sets the country as attacking
                    }else { //else set to defending
                        game.setDefendingCount(c);
                    }

                }else if(game.getPhase() == 3){ //move phase

                    //sets countries that are being moved from and to, if reclicked deselect the country
                    if(game.getActivePlayer().getPlayerNum() == c.getPlayerNum()){
                        if(game.getMoveFromcID() == 0){
                            game.setMoveFromcID(c.getcID());
                        }else if(game.getMoveTocID() == 0){
                            game.setMoveTocID(c.getcID());
                        }else if(game.getMoveTocID() == c.getcID()){
                            game.setMoveTocID(0);
                        }else if(game.getMoveFromcID() == c.getcID()){
                            game.setMoveFromcID(0);
                        }
                    }
                }
              });
          //  System.out.println(c.getNeighborCIDs() +"");//delete
            }
        updateButtons(); //update the buttons display
    }
        public void nextButtonSetup(){//change game phase and player
        next.setOnClickListener(v ->{
            game.phaseChange();
            updateButtons();
        });
        }
}

The most relevant code here is the onCreate method that calls my RiskGame constructor. The RiskGame constructor is only called one time in the onCreate method. The following is my RiskGame class of which the most relevant where the thread is created is on line 83 in the Constructor:

package com.example.risk;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RiskGame implements Serializable {

    Player activePlayer;
    private int activePlayerID;
    int phase;
    ArrayList<Player> players;
    Country attackingCount;
    Country defendingCount;
    ArrayList<Country> countries;
    Random rand;



    int moveFromcID;
    int moveTocID;
    int aDie1;
    int aDie2;
    int aDie3;
    int dDie1;
    int dDie2;

    public RiskGame() {
        players = new ArrayList<Player>();
        for (int i = 1; i < 5; ++i) {
            players.add(new Player(i));
        }
        activePlayer = players.get(0);
        phase = 1;
        attackingCount = new Country();
        defendingCount = new Country();
        countries = new ArrayList<>();
        rand = new Random();
        aDie1 =0;
        aDie2 =0;
        aDie3 =0;
        dDie1 =0;
        dDie2 =0;
        moveFromcID =0;
        moveTocID =0;

        ArrayList<int []> neighbors = new ArrayList<int[]>(); // array of neighbor arrays
        neighbors.add(new int[]{2});
        neighbors.add( new int[]{1, 3, 14, 15});
        neighbors.add(new int[]{2, 4, 16});
        neighbors.add(new int[]{3, 5, 16});
        neighbors.add(new int[]{4, 6, 16});
        neighbors.add(new int[]{5, 7, 8});
        neighbors.add( new int[]{6, 8});
        neighbors.add(new int[]{6, 7, 9, 16, 15});
        neighbors.add(new int[]{8, 10});
        neighbors.add(new int[]{9, 11, 12});
        neighbors.add(new int[]{10});
        neighbors.add( new int[]{10, 13});
        neighbors.add( new int[]{12, 14});
        neighbors.add(new int[]{13, 2});
        neighbors.add( new int[]{2, 8, 16});
        neighbors.add(new int[]{3, 4, 5, 8, 15});


        for(int i = 0; i < neighbors.size(); i++) { //sets up random gameboard
            int j = 0;
            do {
            j = rand.nextInt(4) ;
            }while (players.get(j).getHeldCountries().size() >= 4); //randomly selects a player that doesn't have 4 held countries
            Country c = new Country(j + 1, neighbors.get(i).length+1 , i + 1, neighbors.get(i)); //makes the country
            addCountry(c); //adds to the arraylist
            players.get(j).addCounty(c); //gives country to player
        }

        //Add Client Here

        ExecutorService worker = Executors.newSingleThreadExecutor();
        Client client = null;
        try {
            client = new Client(this);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Client finalClient = client;
        worker.submit(() -> {
            while (!Thread.interrupted())
            finalClient.start();
        });
    }

    public  void addCountry(Country country){
        countries.add(country);
    }
    public Player getActivePlayer() {
        return activePlayer;
    }

    public void setActivePlayer(Player activePlayer) {
        this.activePlayer = activePlayer;
    }

    public ArrayList<Player> getPlayers() {
        return players;
    }

    public void setPlayers(ArrayList<Player> players) {
        this.players = players;
    }

    public RiskGame nextPlayer(){
        if(activePlayer.getPlayerNum() == 4){
            activePlayer = players.get(0);
        }else {
            activePlayer = players.get(activePlayer.getPlayerNum());
        }
        return this;
    }

    public int getPhase() {
        return phase;
    }

    public void setPhase(int phase) {
        this.phase = phase;
    }

    public Country getAttackingCount() {
        return attackingCount;
    }

    public void setAttackingCount(Country attackingCount) {
        this.attackingCount = attackingCount;
    }

    public Country getDefendingCount() {
        return defendingCount;
    }

    public void setDefendingCount(Country defendingCount) {
        this.defendingCount = defendingCount;
    }

    public void setActivePlayerID(int activePlayerID){
        this.activePlayerID = activePlayerID;
    }

    public int getActivePlayerID(){
        return this.activePlayerID;
    }

    public RiskGame attack(){
        if(phase == 2){
            boolean validAttack = false;
            for (int i =0 ; i < attackingCount.getNeighborCIDs().length; i++){
                System.out.println(attackingCount.getNeighborCIDs()[i]);
                if (attackingCount.getNeighborCIDs()[i] == defendingCount.getcID() && attackingCount.getArmiesHeld() >1){
                    validAttack = true;
                }
            }


            if (!validAttack){
                System.out.println("not vaild attack"); //delete
                System.out.println(attackingCount.getcID() + "attack cid" + defendingCount.getcID()); //delete
                System.out.println("attacking cids" + Arrays.asList(attackingCount.getNeighborCIDs()).toString() + " cid: "+ attackingCount.getcID());
               for (int i =0 ; i < attackingCount.getNeighborCIDs().length; i++){
                   System.out.println(attackingCount.getNeighborCIDs()[i]);
               }

                System.out.println("defending cids" + Arrays.asList(defendingCount.getNeighborCIDs()).toString() + " cid: "+ defendingCount.getcID());
                attackingCount = new Country();
                defendingCount = new Country();
            }else {
              rollAttack();
              rollDefence();
              int cId;

              if(aDie1 > dDie1 || aDie2 >dDie1 || aDie3 > aDie1){
                cId  = defendingCount.getcID();
                  countries.get(cId - 1).removeArmy();
              }else if((dDie1 >  aDie1 && dDie1 > aDie2 && dDie1 > aDie3)) {
                   cId = attackingCount.getcID();
                   countries.get(cId - 1).removeArmy();
              }

              if (dDie2 != 0 && attackingCount.getArmiesHeld() > 1){
                    if(aDie1 > dDie2 || aDie2 >dDie2 || aDie3 > aDie2){
                        cId  = defendingCount.getcID();
                        countries.get(cId - 1).removeArmy();
                    }else if((dDie2 > aDie1 &&  dDie2 > aDie2 && dDie2 > aDie3)) {
                        cId = attackingCount.getcID();
                        countries.get(cId - 1).removeArmy();
                  }
              }

              if(defendingCount.getArmiesHeld() <= 0){
                  cId = defendingCount.getcID();
                  countries.get(cId - 1).setPlayerNum(attackingCount.getPlayerNum());
                  countries.get(cId -1).setArmiesHeld(1);

                  cId = attackingCount.getcID();
                  countries.get(cId -1).setArmiesHeld(countries.get(cId -1).getArmiesHeld() - 1);
                  attackingCount = new Country();
                  defendingCount = new Country();
              }
              diceToZero();
              System.out.println( attackingCount.getcID() + " is attacking " + defendingCount.getcID()); //delete
            }

        }
        return this;
    }

    private void diceToZero(){
        aDie1 =0;
        aDie2 =0;
        aDie3 =0;
        dDie1 =0;
        dDie2 =0;
    }

    public RiskGame phaseChange(){
        if(phase != 3){ //if not phase 3 increment phase
            phase+= 1;

        }else { //otherwise set phase to one and change active player
          phase = 1;
          nextPlayer();
          activePlayer.setPlaceablearmies(0);
          for(Country c: countries) {
              if (c.getPlayerNum() == activePlayer.getPlayerNum() ) {
                  activePlayer.setPlaceablearmies(activePlayer.getPlaceablearmies() + c.getArmyValue());
              }
          }
          moveTocID =0;
          moveFromcID =0;
          if(activePlayer.getPlaceablearmies() ==0){
              phaseChange();
              phaseChange();
              phaseChange();
          }

        }
        return this;
    }

    public RiskGame addArmy(int cid){
        countries.get(cid - 1).addArmy();
        return this;
    }


    //Rolls the attacking dice
    private void rollAttack(){
        if(attackingCount.getArmiesHeld() > 3){
            aDie1 = rand.nextInt(7 )+1;
            aDie2 = rand.nextInt(7 )+1;
            aDie3 = rand.nextInt(7 )+1;

        }else if(attackingCount.getArmiesHeld() > 2){
            aDie1 = rand.nextInt(7 )+1;
            aDie2 = rand.nextInt(7 )+1;
        }else {
            aDie1 = rand.nextInt(7 ) +1;
        }
        System.out.println(aDie1 +" " + aDie2 +" " +aDie3);
    }

    //rolls the defence dice
    private void rollDefence(){
        if(defendingCount.getArmiesHeld() > 1){
            dDie1 = rand.nextInt(7 )+1;
            dDie2 = rand.nextInt(7 )+1;
        }else {
            dDie1 = rand.nextInt(7 )+1;
        }
        System.out.println(dDie1 +"  "+ dDie2);
    }

    public RiskGame move(){


        if(phase == 3){
            boolean validMove = false;
            if(moveTocID != 0 && moveFromcID != 0){
                for (int i = 0; i < countries.get(moveFromcID - 1).getNeighborCIDs().length; i++) {
                    if (countries.get(moveFromcID - 1).getNeighborCIDs()[i] == moveTocID && countries.get(moveFromcID - 1).getArmiesHeld() > 1) {
                        validMove = true;
                    }
                }
                if (moveTocID != 0 && moveTocID != moveFromcID && moveFromcID != 0 && validMove) {
                    addArmy(moveTocID);
                    countries.get(moveFromcID - 1).removeArmy();
                }
            }
        }
        moveFromcID =0;
        moveTocID =0;
        return this;
    }

    public Country getCountry(int cid){
        return countries.get(cid - 1);
    }



    public int getMoveFromcID() {
        return moveFromcID;
    }

    public void setMoveFromcID(int moveFromcID) {
        this.moveFromcID = moveFromcID;
    }

    public int getMoveTocID() {
        return moveTocID;
    }

    public void setMoveTocID(int moveTocID) {
        this.moveTocID = moveTocID;
    }
}

Below is my "Client" class:

package com.example.risk;
import android.util.Pair;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;

public class Client extends Thread{

    RiskGame g;
    int ID;
    boolean playerExited = false;
    ReentrantLock lock = new ReentrantLock();
    AtomicBoolean started = new AtomicBoolean(false);

    public Client(RiskGame g) throws IOException {
        this.g = g;
        this.ID = generateID();
    }

    public String writeGameStateToString(){
        String gamestate = "";

        gamestate += this.ID + ":";

        for(Country countries: g.countries){
            gamestate += countries.getcID() + " " + countries.getPlayerNum() + " " + countries.getArmyValue() + ":";
        }

        return gamestate;
    }

    @Override
    public synchronized void run() {
        try {
            lock.lock();
            this.sendData();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public synchronized static int generateID(){
        ZonedDateTime nowZoned = ZonedDateTime.now();
        Instant midnight = nowZoned.toLocalDate().atStartOfDay(nowZoned.getZone()).toInstant();
        Duration duration = Duration.between(midnight, Instant.now());
        long seconds = duration.getSeconds();
        return (int) (seconds%(65535-4999))+4999;
    }

    public synchronized void sendData() throws IOException {
        if(!started.getAndSet(true)) {
            System.out.println(ID);
            Socket sendingSocket = new Socket("10.0.2.2", 4999);
            OutputStream os;
            BufferedOutputStream bos;
            ObjectOutputStream oos;

            os = sendingSocket.getOutputStream();
            bos = new BufferedOutputStream(os);
            oos = new ObjectOutputStream(bos);
            oos.writeObject(writeGameStateToString());

            oos.flush();

            while (!this.playerExited) {
                //Get gamestate back from server
                receiveData();
                os = sendingSocket.getOutputStream();
                bos = new BufferedOutputStream(os);
                oos = new ObjectOutputStream(bos);
                oos.writeObject(writeGameStateToString());

                oos.flush();

            }
            sendingSocket.close();
        }
    }

    public synchronized void receiveData() throws IOException {
        Socket receivingSocket;
        InputStream is;
        BufferedInputStream bis;
        ObjectInputStream ois;
        boolean listening = true;
        while(listening){
            ServerSocket clientListening = new ServerSocket(this.ID);
            receivingSocket = clientListening.accept();
            is = receivingSocket.getInputStream();
            bis = new BufferedInputStream(is);
            ois = new ObjectInputStream(bis);
            try {
                RiskGame temp = (RiskGame) ois.readObject();
                if(temp.getActivePlayerID() == ID) {
                    listening = false;
                    this.g = temp;
                }else{
                    System.out.println(temp.getActivePlayer() + ": Updated local gamestate");
                    this.g = temp;
                }
            } catch (ClassNotFoundException e) {
                System.out.println("Received incompatible Object Type from Server");
            }
            ois.close();
            bis.close();
            is.close();
            clientListening.close();
        }
    }
}

Here is an example of the output I am generating Server side where the first block of output is valid and the second is a completely different RiskGame object that is not displayed on the App and I'm not sure how it's being created:

服务器输出图像 应用程序上的实际游戏 GUI/Board

The problem is "User 2 has joined" there should only be one user so far. I know this is a huge code dump but I wanted to make sure that any relevant code would be included. I hope that I made it clear what sections were most relevant and to re-iterate the most relevant code would be in the onCreate method in the first bit of code, the RiskGame constructor at the bottom in the second bit of code and the sendData method of the Client class.

If you need any information or have any questions/suggestions/criticisms;) feel free to drop em below, I could use whatever help I can get. Also apologies the GUI looks like crap I'm no artist. Haha

i think that the problem can be here

ExecutorService worker = Executors.newSingleThreadExecutor();
    Client client = null;
    try {
        client = new Client(this);
    } catch (IOException e) {
        e.printStackTrace();
    }
    Client finalClient = client;
    worker.submit(() -> {
        while (!Thread.interrupted())
        finalClient.start();
    });

why are u using while loop in submit? if with start method? it can make u more threads. Try it without Thread.interrupted, it will make only one thread with client.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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