简体   繁体   English

Java线程同步问题

[英]Java thread sync issue

This is another issue where my code doesn't work, until I add a print statement into the code, which causes everything to work fine. 这是我的代码无法正常工作的另一个问题,直到我向代码中添加了一条打印语句,这使得一切正常为止。 I've been struggling to get my head around thread synchronization lately but its been slow progress. 最近,我一直在努力使线程同步化,但是进展缓慢。 I want a quick fix that can do whatever the print statement does so I can carry on with this. 我想要一个快速修复程序,它可以执行print语句所执行的任何操作,因此我可以继续进行下去。 The context is a thread that runs in the server, constantly checking for newly registered users. 上下文是在服务器中运行的线程,不断检查新注册的用户。 If the new user doesn't currently exist in the databse, this thread will add it in. 如果新用户当前不在数据库中,则此线程会将其添加到数据库中。

class RecieveThread implements Runnable {

    public void run() {
        while(true) {
            Message msg;
            try {
                msg = comms.receiveUserMessage();
                User newUser;
                newUser = (User)msg.getContents();

                //System.out.println(newUser.getID() + ": " + newUser.getFN());

                for(User user : existingUsers) {
                    if(newUser.getFN().equals(user.getFN()) && newUser.getLN().equals(user.getLN())) {
                        existingUsers.add(user);
                        System.out.println("added: " + newUser.getFN());
                    }
                }
            }catch (ClassNotFoundException | IOException e) {
                e.printStackTrace();
            }
        }
    }
}


public class Comms {
    public Message receiveUserMessage() throws IOException, ClassNotFoundException {
        User user;
        File userMailbox = new File("UserMailbox.txt");
        FileInputStream fis = new FileInputStream(userMailbox);
        ObjectInputStream ois = new ObjectInputStream(fis);

        user = (User)ois.readObject();
        ois.close();

        return new UserMessage(user);
    }
}

public abstract class Message {
    abstract Object getContents();
}

class UserMessage extends Message {
    private User user;

    public UserMessage(User user) {
        this.user = user;
    }

    public User getContents() {
        return this.user;
    }
}


import java.io.Serializable;

public class User implements Serializable {
    private String fn;
    private String ln;
    private int id;
    private char[] pwd;
    private static int userCount = 0;

    public User(String fn, String ln, char[] pwd) {
        this.fn = fn;
        this.ln = ln;
        this.pwd = pwd;
        this.id = ++userCount;
    }

    public String getFN() {
        return fn;
    }

    public String getLN() {
        return ln;
    }

    public char[] getPwd() {
        return this.pwd;
    }

    public int getID() {
        return this.id;
    }
}

Stacktrace: 堆栈跟踪:

java.io.EOFException

at java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)
at java.io.ObjectInputStream$BlockDataInputStream.readUnsignedShort(Unknown Source)
at java.io.ObjectInputStream$BlockDataInputStream.readUTF(Unknown Source)
at java.io.ObjectInputStream.readString(Unknown Source)
at java.io.ObjectInputStream.readTypeString(Unknown Source)
at java.io.ObjectStreamClass.readNonProxy(Unknown Source)
at java.io.ObjectInputStream.readClassDescriptor(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at Comms.receiveUserMessage(Comms.java:40)
at ServerPanel$RecieveThread.run(ServerPanel.java:32)
at java.lang.Thread.run(Unknown Source)
java.io.EOFException
at java.io.ObjectInputStream$BlockDataInputStream.peekByte(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
at java.io.ObjectInputStream.readSerialData(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at Comms.receiveUserMessage(Comms.java:40)
at ServerPanel$RecieveThread.run(ServerPanel.java:32)
at java.lang.Thread.run(Unknown Source)
java.io.EOFException
at java.io.ObjectInputStream$BlockDataInputStream.peekByte(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
at java.io.ObjectInputStream.readSerialData(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at Comms.receiveUserMessage(Comms.java:40)
at ServerPanel$RecieveThread.run(ServerPanel.java:32)
at java.lang.Thread.run(Unknown Source)

Your loop does two things forever. 您的循环永远做两件事。

  1. Read a message off the disk. 从磁盘上读取一条消息。
  2. Check (incorrectly) the new user against a list. 根据列表检查(错误地)新用户。

As there is no resting ( Thread.sleep() or some form of blocking access) in any of these the loop just spins, never letting any other thread have a time-slice . 由于在任何这些循环中都没有休息( Thread.sleep()或某种形式的阻塞访问),所以循环只会旋转,永远不要让任何其他线程具有时间片

Adding the println (which is a blocking method) allows other threads a look-in on the system. 添加println (这是一种阻塞方法)使其他线程可以在系统上进行查找。

Your mistake in checking the user list - imagine what will happen if the existingUsers list is empty. 您在检查用户列表时遇到的错误-想象一下如果existingUsers列表为空会发生什么。

Remember that Java implements Cooperative multitasking on a PC with only one core so all threads must give time to others at regular intervals. 请记住,Java在仅具有一个内核的PC上实现了协作式多任务处理 ,因此所有线程必须定期分配时间给其他线程。

It is likely that the message file either does not exist or is empty. 消息文件可能不存在或为空。

I'd suggest a couple things: 我建议一些事情:

1) Remove the while loop from your ReceiveThread class. 1)从ReceiveThread类中删除while循环。 Then, where you submit it to a thread pool, use a scheduled one instead, something like: 然后,在将其提交到线程池的地方,改用预定的线程池,例如:

private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

ReceiveThread rt = new ReceiveThread();
scheduler.scheduleAtFixedRate(rt, 0, YOUR_RATE, TimeUnit.SECONDS);

This will cause it to execute periodically, so you won't have an unchecked loop spinning. 这将导致它定期执行,因此您不会有未经检查的循环旋转。

2) For debugging, disable the thread pool and just call ReceiveThread directly instead. 2)为了进行调试,请禁用线程池,而直接直接调用ReceiveThread。 Ex: 例如:

ReceiveThread rt = new ReceiveThread();
rt.run();

Then you can more easily step through and figure out the exception if needed. 然后,如果需要,您可以更轻松地逐步解决并找出异常。

In any case, wrap the ois.readObject() call in a try/catch block to handle the EOFException. 无论如何,将ois.readObject()调用包装在try / catch块中以处理EOFException。

Based on your comment saying a sleep statement 'fixes' it just like a print statement, my guess is that existingUsers is the problem. 根据您的评论,说一条sleep语句像一条print语句一样“修复”了它,我的猜测是existingUsers是问题所在。 In your code above you're not doing anything to make it thread-safe. 在上面的代码中,您没有做任何事情来使其成为线程安全的。 You need something along the lines of: 您需要遵循以下要求:

synchronized (existingUsers)
{
    existingUsers.add(user);
}

You need to do this every time you want to modify existingUsers other than when you initialize it. 除了初始化时,每次需要修改existingUsers时都需要执行此操作。 Here's a brief entry on synchronized . 这是有关synchronized的简短条目 It's more complicated than I'm stating here, but this is a good place to start. 比我在这里说的要复杂得多,但这是一个不错的起点。

The thing is, print statements take a loooong time. 事情是,打印报表需要过长...时间。 At least, from the computer's perspective. 至少从计算机的角度来看。 So by having a print statement, you're essentially delaying the execution of the code below. 因此,通过使用一条print语句,您实际上在延迟下面代码的执行。 And in your program, this delay seems to be enough to make things run smoothly. 而且在您的程序中,这种延迟似乎足以使事情平稳运行。 I suspect that something is happening with existingUsers elsewhere, but this something happens only once and it happens very early on. 我怀疑其他地方的existingUsers正在发生某种情况,但是这种情况仅发生一次并且很早就发生了。 By having the delay, you pass over whatever this happens to be and it never causes a problem. 通过延迟,您可以忽略任何可能发生的情况,并且永远不会造成问题。

It's common for multi-threaded exceptions to be really difficult to decipher. 通常很难解密多线程异常。 You're getting an EOFException but that might be a red herring. 您将收到EOFException但这可能是一个红色鲱鱼。 If you were having problems reading the file, I'd expect that exception every time you run it - not just when a print/sleep statement is in there. 如果您在读取文件时遇到问题,我希望每次运行它时都会出现该异常,而不仅仅是在其中有一个print / sleep语句的时候。

You're trying to read your UserMailbox.txt file while it is still being written by an ObjectOutputStream not shown in the code you gave. 您正在尝试读取UserMailbox.txt文件,而该文件仍由您提供的代码中未显示的ObjectOutputStream编写。 Pulling on it endlessly in the while loop causes the problem, and the delay incurred by the System.out.println() is enough for the "pushing" thread to finish writing the file, which becomes ok to read from. while循环中无休止地拖拉它会导致问题,并且System.out.println()所引起的延迟足以使“推送”线程完成写入文件,可以读取该文件。

Try to implement a synchronization mechanism to tell your thread that the file has been fully written and is safe to read. 尝试实现一种同步机制,以告诉您的线程文件已被完全写入并且可以安全读取。 If you can't implement such, at least wait for the file to stop being modified before reading it: 如果无法实现,请至少等待文件被修改后再读取:

File f = new File("UserMailbox.txt");
long t0 = f.lastModified(), t;
Thread.sleep(100);
while ((t = f.lastModified()) != t0) {
    Thread.sleep(100);
    t0 = t;
}

That ugly hack will tell you the file is complete. 难看的骇客会告诉您文件已完成。 But you should definitely have another mechanism of telling it is ready (eg send a message). 但是,您肯定应该有另一种机制来告知它已准备就绪(例如,发送消息)。

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

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