简体   繁体   中英

Accessing variable concurrently from two different threads in java

I have been getting an error while trying to access a variable from two different threads. I'm asking for help because the help will be relevant to my code. My question is, how do I access a variable at the same time from two different threads.

What I have:

I have a ObjectHandler that holds all of my objects. I have a Client class that is sending data to the server. I have a game class that is rendering and keeping track of all of the local variables.

My client class needs to access the Object handler to send to the player and bullets to the server and my game class needs to access the objects to render them. My game class and my client class each have their own threads.

Object Handler

public class ObjectHandler implements Serializable{
    private static final long serialVersionUID = 4515552114741927916L;

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock readLock = readWriteLock.readLock();
    private Lock writeLock = readWriteLock.writeLock();

    //////HANDLERS//////
    private KeyInputHandler keyhandler;
    private CollisionHandler collisionHandler;
    private MouseInputHandler MouseInput;

    private Player player;
    private ArrayList<Entity>objects;

    public ObjectHandler(){
        MouseInput = new MouseInputHandler(this);
        keyhandler = new KeyInputHandler();

        objects = new ArrayList<Entity>();
        player = new Player(40,40,20,20,ObjectID.Player,this);
    }

    public synchronized KeyInputHandler getKeyhandler() {return keyhandler;}
    public synchronized CollisionHandler getCollisionHandler() {return collisionHandler;}
    public synchronized MouseInputHandler getMouseInput() {return MouseInput;}

    public synchronized Player getPlayer() {return player;}
    public synchronized void setPlayer(Player player) {this.player = player;}

    public synchronized ArrayList<Entity> getObjects() {return objects;}
    public synchronized void setObjects(ArrayList<Entity> objects) {this.objects = objects;}

}

Client Class

 package com.Nickhulsey.network;


import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import com.Nickhulsey.game.ObjectID;
import com.Nickhulsey.handlers.ObjectHandler;

public class Client implements Runnable{

    Thread Receive;
    ObjectHandler game;

    private InetAddress ipAddress;
    private Socket socket;
    private ObjectOutputStream outToServer;
    private ObjectInputStream inFromServer;

    private Packet out;
    private Packet in;

    public Client(ObjectHandler game, String ipAddress){
        this.game = game;
        in = new Packet();
        out = new Packet();
        try {
            this.ipAddress = InetAddress.getByName(ipAddress);
            socket = new Socket(ipAddress,9000);
            System.out.println("Started!");
        } catch (UnknownHostException e) {
        } catch (IOException e) {
        }
        Receive = new Thread(this);
        Receive.start();
    }

    public synchronized void run() {

        while(true){
            Send();
             try {
                inFromServer = new ObjectInputStream(socket.getInputStream());
                in = (Packet) inFromServer.readObject();
            } catch (IOException e) {
            } catch (ClassNotFoundException e) {
            }
            //unpack our data
            if(in != null){
                unPack(in);
            }
        }
    }

    public void unPack(Packet in){
        //unpack our connected players IF there is any
        game.getObjects().clear();
        //set in's objects to game's objects
        for(int i = 0; i < in.getObjects().size();i++){
            if(in.getObjects() != null){
                game.getObjects().add(in.getObjects().get(i));
            }
        }
    }

    public void Send(){
        try {
            outToServer = new ObjectOutputStream(socket.getOutputStream());
            out = new Packet();

            //set data for our out package
            out.setPlayer(game.getPlayer());
            out.setObjects(game.getObjects());
            outToServer.writeObject(out);

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

and my Game class

package com.Nickhulsey.game;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.io.Serializable;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

import com.Nickhulsey.handlers.ObjectHandler;
import com.Nickhulsey.network.Client;
import com.Nickhulsey.network.Server;

//THREADS ARE NOT SERIALIZABLE
public class Game extends Canvas implements Runnable, Serializable{
    private static final long serialVersionUID = 8279766339522266301L;

    private Thread thread;
    private Client gameClient;
    private Server gameServer;

    public final int SCALE = 1; 
    public int Server;
    boolean running = false;

    ObjectHandler ObjectHandler;

    public Game(){
        //start the game
        ObjectHandler = new ObjectHandler();

        Server = JOptionPane.showConfirmDialog(null,"Run the server?","Server",JOptionPane.YES_NO_OPTION);

        if(Server == JOptionPane.YES_OPTION){
            gameServer = new Server();
        }else{
            String IpConnect = JOptionPane.showInputDialog("Enter Ip to connect to.(25.156.181.27)");
            gameClient = new Client(ObjectHandler,IpConnect);
        }

    }

    public synchronized void start() {
        if (running) {
            return;
        }
        running = true;
        thread = new Thread(this);
        thread.start();
    }

    public synchronized void run() {
        while (running){
            if(Server != JOptionPane.YES_OPTION){
              long lastTime = System.nanoTime();
              double amountOfTicks = 60;
              double ns = 1000000000.0D / amountOfTicks;
              double delta = 0.0D;
              long timer = System.currentTimeMillis();
              int updates = 0;
              int frames = 0;

              while (running){
                long now = System.nanoTime();
                delta += (now - lastTime) / ns;
                lastTime = now;
                while (delta >= 1){
                  tick();
                  updates++;
                  delta -= 1;
                  render();
                }

                frames++;

                if (System.currentTimeMillis() - timer > 1000L){
                  timer += 1000L;
                  frames = 0;
                  updates = 0;
                }
              }
            }
        }
       //stop();
    }

    public void render() {
        BufferStrategy bs = getBufferStrategy();
        if (bs == null)
        {
          createBufferStrategy(3);
          return;
        }
        Graphics g = bs.getDrawGraphics();

        g.setColor(Color.white);
        g.fillRect(0,0, 5000, 5000);

        for(int i = 0; i < ObjectHandler.getObjects().size(); i++){
            ObjectHandler.getObjects().get(i).Draw(g);
        }

        ObjectHandler.getPlayer().Draw(g);

        g.dispose();
        bs.show();
    }

    public void tick() {
        //for(int i = 0; i < objects.size();i++){objects.get(i).Update(objects);}
        ObjectHandler.getPlayer().Update(ObjectHandler.getObjects());
    }


    public static void main (String [] args){
        JFrame window = new JFrame("A Multiplayer GAME");
        Game game = new Game();

        window.setSize(300,300);
        game.addKeyListener(game.ObjectHandler.getKeyhandler());
        game.addMouseListener(game.ObjectHandler.getMouseInput());
        game.addMouseMotionListener(game.ObjectHandler.getMouseInput());
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setLocationRelativeTo(null);
        window.add(game);
        window.setVisible(true);
        game.start();
    }

    public ObjectHandler getObjectHandler() {return ObjectHandler;}
}

I know that this is a lot, sorry. My issue is that I'm accessing an arraylist of bullets and edit it, while another thread is trying to access it. I have tried synchronized, and I have tried using the lock classes. Can anyone suggest a way around this? Sorry if I didn't ask a real question or didn't follow the stack overflow guide lines, I'm still really new.

EDIT: Error Log:

Exception in thread "Thread-3" java.util.ConcurrentModificationException
    at java.util.ArrayList.writeObject(ArrayList.java:573)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
    at com.Nickhulsey.network.Client.Send(Client.java:78)
    at com.Nickhulsey.network.Client.run(Client.java:45)
    at java.lang.Thread.run(Thread.java:695)

how do I access a variable at the same time from two different threads?

You are already doing that, that is why you get the ConcurrentModificationException .
To preven the exception, use a class from java.util.concurrent for the variable or make sure the threads use the variable exclusively. In your code the objects -list in ObjectHandler is used at the same time by two threads in Game.tick() and Client.unpack()/Send() . To prevent this from happing:

  • add a shared lock object to ObjectHandler public final Object updateLock = new Object;
  • in Game.tick() start the method with synchronized(ObjectHandler.updateLock) { ... method code ... }
  • in Client.unpack()/Send() start the methods with synchronized(game.updateLock) { ... method code ... }

The synchronized keyword will now guarantee that only one of the three synchronized blocks is executed at any given time (since they all use the same object to synchronize against).

The updateLock object is not strictly necessary, you could just use the ObjecHandler object itself for synchronization, but it is good practice to make synchronization explicit in these kind of cases by using a separate object for locking.

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