0

I want to create a program that:

  • Does a long running task, lets say ChildJob
  • Pauses the main task ChildJob, and commits the work done every few seconds

I came up with this code that schedules two threads, will commit every few seconds (commit part is a sop statement at present), and also run ChildJob in parallel.

The problem I am facing is, I am not able to synchronize the two threads properly.

  • while commit() is being called, the ChildJob thread keeps processing. How do I make the ChildJob thread wait?

I understand that making the process() method synchronized is not an option as in that case, the commit() job doesn't even run.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.Random;
import java.util.Date;


class Main {

    // Processing thread, commiting thread
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

    public static void main(String[] args) {

        Main app = new Main();
        ChildJob workUnit = new ChildJob();

        Thread childJobThread = new Thread(new Runnable() 
        { 
            @Override
            public void run() 
            { 
                System.out.println("childJobThread: "+ Thread.currentThread().getName()+" Start. Time = " + new Date());
                try{ workUnit.process(); }
                catch(InterruptedException e){ e.printStackTrace(); } 
            } 
        }); 
        Thread committerThread = new Thread(new Runnable() 
        { 
            @Override
            public void run() 
            { 
                try{ workUnit.commit();  }
                catch(InterruptedException e){ e.printStackTrace(); } 
            } 
        }); 

        final ScheduledFuture<?> commitHandle = app.scheduler.scheduleAtFixedRate(committerThread, 1, 8, TimeUnit.SECONDS);
        final ScheduledFuture<?> jobHandle = app.scheduler.schedule(childJobThread, 0, TimeUnit.SECONDS);
        /*
        Makes the commitHandle run for 60 * 60 seconds, not needed, manually terminating currently.
        app.scheduler.schedule(new Runnable() {
                public void run() {
                    commitHandle.cancel(true);
                }
            }, 
            60 * 60, TimeUnit.SECONDS);*/
    }
}

class ChildJob {
    private int i = 0;

    public void process() throws InterruptedException 
    { 

         // synchronized(this) 
         // {   
            while(true) {
                System.out.println("ChildJob processing at: " + Thread.currentThread().getName() + " : " + new Date() + "----------: " + i++);
                Thread.sleep(1000);
            }
        //} 
    } 

    public void commit() throws InterruptedException 
    {   
        synchronized(this) 
        { 
            System.out.println("\ncommitterThread: " + Thread.currentThread().getName()+" Start. Time = " + new Date());
            // 3s sleep to check consistency from processing.
            Thread.sleep(3000);
            System.out.println("committerThread: " + Thread.currentThread().getName()+"     End. Time = " + new Date() + "\n");
        } 
    } 
}

Result right now:

childJobThread: pool-1-thread-1 Start. Time = Mon May 13 17:51:31 IST 2019
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:31 IST 2019----------: 0

committerThread: pool-1-thread-2 Start. Time = Mon May 13 17:51:32 IST 2019
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:32 IST 2019----------: 1
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:33 IST 2019----------: 2
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:34 IST 2019----------: 3
committerThread: pool-1-thread-2     End. Time = Mon May 13 17:51:35 IST 2019

ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:35 IST 2019----------: 4
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:36 IST 2019----------: 5
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:37 IST 2019----------: 6
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:38 IST 2019----------: 7
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:39 IST 2019----------: 8

committerThread: pool-1-thread-2 Start. Time = Mon May 13 17:51:40 IST 2019
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:40 IST 2019----------: 9
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:41 IST 2019----------: 10
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:43 IST 2019----------: 11
committerThread: pool-1-thread-2     End. Time = Mon May 13 17:51:43 IST 2019

ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:44 IST 2019----------: 12
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:45 IST 2019----------: 13
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:46 IST 2019----------: 14
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:47 IST 2019----------: 15
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:48 IST 2019----------: 16

Desired result:

childJobThread: pool-1-thread-1 Start. Time = Mon May 13 17:51:31 IST 2019
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:31 IST 2019----------: 0

committerThread: pool-1-thread-2 Start. Time = Mon May 13 17:51:32 IST 2019
committerThread: pool-1-thread-2     End. Time = Mon May 13 17:51:35 IST 2019

ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:35 IST 2019----------: 1
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:36 IST 2019----------: 2
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:37 IST 2019----------: 3
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:38 IST 2019----------: 4
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:39 IST 2019----------: 5

committerThread: pool-1-thread-2 Start. Time = Mon May 13 17:51:40 IST 2019
committerThread: pool-1-thread-2     End. Time = Mon May 13 17:51:43 IST 2019

ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:44 IST 2019----------: 6
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:45 IST 2019----------: 7
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:46 IST 2019----------: 8
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:47 IST 2019----------: 9
ChildJob processing at: pool-1-thread-1 : Mon May 13 17:51:48 IST 2019----------: 10
Chinmoy
  • 1,750
  • 2
  • 21
  • 45
  • 1
    Why pause the thread and commit from another ? You can just commit from the ChildJob thread - much simpler and reliable. – Svetlin Zarev May 13 '19 at 12:39
  • Because, I need to make my commit event consistent based on time. Say, every (n) seconds. And the ChildJob.process() method can scale up or down based on the volume of processing so there is no point in the code where I can put a commit() code. I am trying to implement a checkpoint/autosave kind of feature. – Chinmoy May 13 '19 at 13:58

1 Answers1

-3

Try syncronize in the while, say:


while(true) {
  synchronized(this) 
    {   
      System.out.println("ChildJob processing at: " + Thread.currentThread().getName() + " : " + new Date() + "----------: " + i++);
      Thread.sleep(1000);
    }
  } 
}

The idea is, the commitshould has chance to break into running of job.

A sample output

childJobThread: pool-1-thread-1 Start. Time = Mon May 13 20:42:46 CST 2019
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:46 CST 2019----------: 0
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:47 CST 2019----------: 1
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:48 CST 2019----------: 2
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:49 CST 2019----------: 3
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:50 CST 2019----------: 4
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:52 CST 2019----------: 5
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:53 CST 2019----------: 6

committerThread: pool-1-thread-2 Start. Time = Mon May 13 20:42:54 CST 2019
committerThread: pool-1-thread-2     End. Time = Mon May 13 20:42:57 CST 2019

ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:57 CST 2019----------: 7
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:58 CST 2019----------: 8
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:42:59 CST 2019----------: 9
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:00 CST 2019----------: 10
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:01 CST 2019----------: 11

committerThread: pool-1-thread-2 Start. Time = Mon May 13 20:43:02 CST 2019
committerThread: pool-1-thread-2     End. Time = Mon May 13 20:43:05 CST 2019

ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:05 CST 2019----------: 12
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:06 CST 2019----------: 13
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:07 CST 2019----------: 14
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:08 CST 2019----------: 15
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:09 CST 2019----------: 16
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:10 CST 2019----------: 17
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:11 CST 2019----------: 18
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:12 CST 2019----------: 19
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:13 CST 2019----------: 20
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:14 CST 2019----------: 21
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:15 CST 2019----------: 22
ChildJob processing at: pool-1-thread-1 : Mon May 13 20:43:16 CST 2019----------: 23
John
  • 1,654
  • 1
  • 14
  • 21
  • This will simply not work. A better approach is to use the build-in primitives, such as `reentrant lock`, condvars, latches, etc. Yet it's even better to completely avoid teh synchronisation problem, by doing everything in one thread. The benefit of multiple threads is that you can do parallel processing. If you synchronise and thus serialize the processing, you just add complexity and loose the benefits. Thus it's pointless. – Svetlin Zarev May 13 '19 at 12:43
  • @SvetlinZarev Sorry I'm not quite sure the 'simply not work' part. Got it runs well on my laptop, the 'commit' runs between runing of 'job's, and no 'job' runs during 'commit'. I think it is excatly what OP wanted. – John May 13 '19 at 12:45
  • A loop that periodically unlocks a lock and then _immediately_ locks it again is a recipe for thread starvation. – Solomon Slow May 13 '19 at 12:49
  • @SolomonSlow I admit it is not a best solution for the issue, but it works. Also I think this simple approach would help OP understand `synchronized` that, it is an option. – John May 13 '19 at 12:52
  • Maybe it works for you. I have seen many questions on this forum from newbies wondering why that same construct construct did _not_ work in the language and library and operating system and hardware platform that they happened to be using. – Solomon Slow May 13 '19 at 12:56
  • 1
    A solution based on `time` is by its essence - unreliable. Imagine you run the same app on a fast server class machine and on a raspberry pi. On the server it may process 100k elements per second, but on th ePi it may process less than 1 element per second. Do you see the issue with time based `commit` here ? Also if the timer thread fails with a RuntimeException, it will never be rescheduled again, thus it will never call `commmit` – Svetlin Zarev May 13 '19 at 13:00