简体   繁体   中英

Threads occasionally blocking while writing to the same file

I am trying to read from two text files (having the same number of lines, ie 10000) using two separate threads, one file containing Strings and the other containing Integers. Afterwards I want to write in another text file (String,Integer) values from corresponding positions in the result sets, each on a distinct line. For these purposes I'm using two classes implementing the Runnable interface.

The problem I'm facing is that every time I run the program, there is a chance that one of the threads is blocking execution, and I don't understand why considering that I'm using wait() and notify() in a synchronized block. More specifically, at times the program runs successfully finishing execution, while in most cases it stops at various stages (eg ~3000 out 10000 lines written, or even ~500).

citireInteger is a reference to the other thread which is reading the Integer file.

Here is relevant code from the classes dealing with reading from the String and Integer files, respectively:

@Override
public void run(){

    ........
    try {
        pw = new PrintWriter(new BufferedWriter(new FileWriter("rezultat.txt", true)));

        for(int i = 0; i < strings.size(); i++){

            synchronized(this){
                pw.write(strings.get(i));
            }

            synchronized(citireInteger){
                citireInteger.notifyAll();
            }

            try{
                synchronized(this){
                    wait();
                }
            } catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    } catch(FileNotFoundException e){
        e.printStackTrace();
    } catch(IOException e){
        e.printStackTrace();
    } finally{
        pw.close();
    }

    synchronized(citireInteger){
        citireInteger.notify();
    }
}

The thread reading the Integer file:

    public void run(){

    ...........

    try{
        pw = new PrintWriter(new BufferedWriter(new FileWriter("rezultat.txt", true)));

        for(int i = 0; i < ints.size(); i++){

            synchronized(this){
                pw.write(ints.get(i) + '\n');
                System.out.println(ints.get(i));
            }
            synchronized(citireString){
                citireString.notifyAll();
            }

            try{
                synchronized(this){
                    wait();
                }
            } catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    } catch(FileNotFoundException e){
        e.printStackTrace();
    } catch(IOException e){
        e.printStackTrace();
    } finally{
        pw.close();
    }

    synchronized(citireString){
        citireString.notify();
    }
}

What's happening is that one thread is calling notifyAll on the other thread, then getting switched out before synchronizing on its own writer to call wait . The other thread does its write, synchronizes on its writer, and calls wait . Then the first thread gets switched in again, synchronizes on its writer, and calls wait . There's your deadlock.

Better would be to have both threads synchronize on the same object to write. Best would be to store the data in memory in a thread safe way and write from a single thread once reading from both threads was complete.

When you notify the other thread, the other thread will only see that when the other threads is waiting for it. I the other thread is outside that synchronized block, and enters that synchronized block after it is notified, it will then wait for a notify that won't happen, or rather, than happened before it started to wait for it. It's hard to really determine what the objective of this exercise is, but I'm guessing it is about two threads taking turns doing a write. Ok, so, how about making the synchronized(citireInt/String) block encompass its entire code! This way, the other thread trying to obtain a lock will ONLY get that lock WHEN the other thread is waiting. That I think would guarantee no blocking and the result would be both threads taking turns, one by one.

Edit: not entirely exactly as I described, but the key points is where to synchronize...

   public void run()
   {
       ...........

       try
       {
           pw = new PrintWriter(new BufferedWriter(new FileWriter("rezultat.txt", true)));

           synchronized(this)
           {
               for(int i = 0; i < ints.size(); i++)
               {
                   pw.write(ints.get(i) + '\n');
                   System.out.println(ints.get(i));

                   synchronized (citireString)
                   {
                       citireString.notifyAll();
                   }

                   try
                   {
                       wait();
                   }
                   catch (InterruptedException e)
                   {
                       e.printStackTrace();
                   }
               }
           }
       }
       catch (Throwable e)
       {
           e.printStackTrace();
       }
       finally
       {
           pw.close();
       }
    }

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