1

I have following worker class -

public class Worker implements Runnable {
    private int workId;

    public Worker(int workId) {
        this.workId = workId;
    }

    private int count = 0;

    private synchronized void increment() {
        System.out.println("Work id: " + workId);
        System.out.println("Incrementing...");
        Thread.sleep(5000);
        count++;
        System.out.println("Incremented");
    }

    @Override
    public void run() {
        increment();
    }
}

I have following main method -

ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 1; i <= 10; i++) {
            executorService.submit(new Worker(i));
        }
        executorService.shutdown();
        System.out.println("All tasks submitted");
        executorService.awaitTermination(1, TimeUnit.DAYS);
        System.out.println("All tasks completed");

Here increment() has been synchronized. So when 1 thread is occupying it another thread has to wait until that thread leaves the lock.

But when I am submitting the work using a thread pool of 2 threads, both threads seem to be using increment() at the same time.

So here how can I force the two threads to use the increment() method only one at a time?

Payel Senapati
  • 1,134
  • 1
  • 11
  • 27
  • What do you want to have at the end? Right now you have 10 `Worker` objects, with 10 *separate* counters. – tevemadar Jan 05 '21 at 15:43
  • I want that when 1 worker is working on a synchronized task, that worker must have an exclusive lock at the task and another worker cannot do the same task until the firsst worker completes the task and releases the lock. – Payel Senapati Jan 05 '21 at 15:49
  • 1
    You synchronize on the worker object (`this`). If you have several worker objects, then they can indeed run in parallel. – Johannes Kuhn Jan 05 '21 at 15:51

1 Answers1

2
private synchronized void increment() 

This method locking works at the Object level, so if you have two Objects, they won't block each other when calling this, as each one would call its own increment() method (there is no concurrent call on the same Worker instance).


In order to avoid different instances accessing the increment() method concurrently, you need synchronization at Class level, which is achieved when the lock is the same for all of your Worker instances. Some options to declare the lock:

  • Shared Object

    public class Boss extends RichDad implements EarnLotsOfMoney
    {
       private final Object masterLock;
       public Boss() 
       {
          masterLock = new Object();
       }
       public Worker createWorker(int slaveId) 
       {
          return new Worker(masterLock, slaveId);
       }
       //...
    }
    

    yep silly example i know..

    public class Worker implements Runnable 
    {
       private final Object lock;
       private int workId;
    
       public Worker(Object lock, int workId) 
       {
          this.lock = lock;
          this.workId = workId;
       }
    
       private void increment() 
       {
           synchronized(lock) /*lock holds the same reference in all instances*/
           {
               //...
           }
       }
    
       @Override
       public void run() {
           increment();
       }
    }
    

lock is created only once and then is passed as a parameter when creating the Worker instances. This would block all Worker instances created from the same Boss (in this approach the lock is a non-static object).


  • Its own Class

    public class Worker implements Runnable 
    {
       private int workId;
       public Worker(int workId) {
          this.workId = workId;
       }
    
       private int count = 0;
    
       private void increment() 
       {
           synchronized(Worker.class) /*class level lock here*/
           {
             System.out.println("Work id: " + workId);
             System.out.println("Incrementing...");
             Thread.sleep(5000);
             count++;
             System.out.println("Incremented");
           }
       }
    
       @Override
       public void run() {
           increment();
       }
    }
    

This would synchronize the threads using the shared Worker class as lock.

aran
  • 10,978
  • 5
  • 39
  • 69
  • Thank you, is it possible to create two Worker.class locks? Like we do in creating Object locks? Object lock1 = new Object(); Object lock2 = new Object(); – Payel Senapati Jan 05 '21 at 15:46
  • 1
    Take a look here, for example: https://stackoverflow.com/a/18356932/2148953 – aran Jan 05 '21 at 15:48
  • 1
    The concept is extremely clear now, thank you so much – Payel Senapati Jan 05 '21 at 15:52
  • @PayelSenapati my last assumption about locked objects is not totally correct, as non-static objects could also perform a class level locking; I included an example of this, based on sharing the same instance of an object to the instance constructors. Hope it also helps! – aran Jan 07 '21 at 00:14