4

So I'm new to this Thread stuff and I wrote a simple program to test avoiding Race Conditions. My first attempt was with Named Inner classes :

/* App1.java */

package ehsan;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class App1{
    private final int poolSize = 10;
    private final int numLoop = 5;
    private int lastThread = 0;

    public App1() {
        ExecutorService taskList = Executors.newFixedThreadPool(poolSize);
        for (int i = 0;i < poolSize;i++) {
            taskList.execute(new Counter());
        }
        taskList.shutdown();
    }

    private class Counter implements Runnable{

        @Override
        public void run() {
            synchronized (this) {
                int currentThread = lastThread;
                System.out.println("Current thread : "+currentThread);
                lastThread = lastThread + 1;
            }
            System.out.println("Thread was executed");
        }
    }

}

and App1Test.java :

package ehsan;

import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        new App1();
    }
}

So as a result it showed :

Current thread : 0
Thread was executed
Current thread : 1
Thread was executed
Current thread : 1
Thread was executed
Current thread : 3
Thread was executed
Current thread : 4
Thread was executed
Current thread : 5
Thread was executed
Current thread : 6
Thread was executed
Current thread : 7
Thread was executed
Current thread : 6
Current thread : 8
Thread was executed
Thread was executed

And whole things got mixed up and I'm facing Race conditions here even when I've use synchronized there.

But my second attempt worked! :

package ehsan;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class App1 implements Runnable{
    private final int poolSize = 10;
    private final int numLoop = 5;
    private int lastThread = 0;

    public App1() {
        ExecutorService taskList = Executors.newFixedThreadPool(poolSize);
        for (int i = 0;i < poolSize;i++) {
            taskList.execute(this);
        }
        taskList.shutdown();
    }

    @Override
    public void run() {
        synchronized (this) {
            int currentThread = lastThread;
            System.out.println("Current thread : "+currentThread);
            lastThread = lastThread + 1;
            System.out.println("Thread was executed");
        }
    }
}

And the result was as I expected :

Current thread : 0
Thread was executed
Current thread : 1
Thread was executed
Current thread : 2
Thread was executed
Current thread : 3
Thread was executed
Current thread : 4
Thread was executed
Current thread : 5
Thread was executed
Current thread : 6
Thread was executed
Current thread : 7
Thread was executed
Current thread : 8
Thread was executed
Current thread : 9
Thread was executed

So my question is why my first attempt didn't work and the second one worked greatly? Thanks for helping me, I'm a beginner in Multi-Threaded programming!

  • Synchronizing the entire body of a thread's `run()` method is almost always a mistake. In your second, "working" example, the `synchronized` block prevents any of your threads from running at the same time as any of its peers. But if you don't allow the threads to run at the same time, then what's the point of using threads? – Solomon Slow Jul 06 '16 at 12:55

3 Answers3

1

In the first program, you create a different Counter instance as the Runnable whose run() method is executed by each thread, so synchronized (this) uses a different lock for each thread, and therefore the code is not thread safe. If you use the same Counter instance instead of creating a new one for each thread, this program will also behave as you expected.

    Counter counter = new Counter();
    for (int i = 0;i < poolSize;i++) {
        taskList.execute(counter);
    }

In the second program, you use the same App1 instance as the Runnable whose run() method is executed by all the threads, so synchronized (this) uses the same locks for all the threads.

Eran
  • 387,369
  • 54
  • 702
  • 768
0

With a great deal of research I found few ways to solve this.

Please Note : Some of these solutions were pointed out by Eran, Thomas and etc. and I thank them for helping me but I just wanted to collect all the possible solutions in a single answer so the future visitors of this post find the answers easily.

  • For Named Inner Classes :

Instead of using this as the locking object for synchronization we can use the OutterClass instance :

 synchronized (App1.this) {
        int currentThread = lastThread;
        System.out.println("Current thread : "+currentThread);
        lastThread = lastThread + 1;
 }
  • For Separated Classes :

Solution 1


Synchronizing on an object we are receiving from the Caller Class( The class containing the task list and ... ). Here is an example :

public App1 implements Runnable {

    private final Integer shared;/* Not spending time on auto-boxing */

    public App1(Integer sharedNum) {
        shared = sharedNum;
    }

    @Override
    public void run() {
        synchronization(shared){
            //code here
        }
    }
}

public App1Test {
    private final int forSharing = 14;
    public static void main(String[] args) {
        ExecutorService taskList = Executors.newFixedThreadPool(poolSize);
        taskList.execute(new App1(forSharing));
        // and lob lob
    }
}

Solution 2


Synchronization on class object :

synchronized (App1.class) { /* As the fields and methods of class App1 are the same for all objects */
    int currentThread = lastThread;
    System.out.println("Current thread : "+currentThread);
    lastThread = lastThread + 1;
}

Solution 3


Synchronizing on an static field ( Which I loved it because its really innovative ) :

/* Add this field to class definition */
private static Object sharedObject = new Object();

/* Now in `run` method  use the object like this : */
synchronized(sharedObject) {
    //TODO : write your code here :)
}

So these are the solutions to this sneaky problem which is a bit hard to debug :)

I hope this helps fellows who have faced the same problem :)

Cheers, Ehsan

  • Solution 4: [AtomicInteger](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html). Which does not really answer your question but solves that problem you face in the question's example. – Fildor Jul 06 '16 at 13:03
  • The AtomicInteger? Basically what the name sais. An Integer with atomic operation ( for example "[getAndIncrement](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html#getAndIncrement--)" ). If you share one of these, you can avoid synchronization blocks, depending on your implementation. So in your "counter" case, with an AtomicInteger you could ditch the `synchronized` and have one line doing all the three: `System.out.println("Current thread : "+sharedInt.getAndIncrement());` – Fildor Jul 06 '16 at 13:19
  • Um, thanks I didn't know if it could be used this easily :) –  Jul 06 '16 at 13:24
-1

synchronized (this) in Counter synchronizes on the instance, so if you pass a new instance to each thread (taskList.execute(new Counter());) there won't be any synchronization at all (since no 2 threads use the same instance to synchronize on). Thus either use synchronized(Counter.class) or some other shared monitor object (since Counter is an inner class of App1 you could use synchronize(App1.this)).

Your second approach works since you pass the same instance to each thread: taskList.execute(this);.

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • It explains the reason why it works in one implementation. But misses the background explaination. – Konrad Jul 06 '16 at 09:11
  • Just a sidenote: Can we still call it "concurrency" if we synchronize the `run` method? Having the expected output doesn't mean synchronization is done right. This is an example for exactly that. Execution is sequentialized instead of synchronizing the critical resource (which is the counter). – Fildor Jul 06 '16 at 09:13
  • @Konrad what background explanation would you expect? How synchronization works in general? – Thomas Jul 06 '16 at 12:01
  • @Fildor well the question (and hence the answer) wasn't about concurrency in general but about a specific part of that. In the general sense synchronization is an integral part of concurrency - in the OP's case you'll get a sequential execution yet in the background you still have concurrency since multiple threads try to concurrently run the synchronized block (they just can't). ... – Thomas Jul 06 '16 at 12:07
  • @Fildor ... Also there's another difference between the current code and real sequential execution: you can't influence in which order the threads run besides the order in which they are started while in truly sequential code it is deterministic in that you know the code will be executed in a defined sequential order. Don't get confused by the seemingly sequential output 0,1,2,3, ... - that reflects the number of calls so far, not the order in which the threads are running. – Thomas Jul 06 '16 at 12:09
  • @Thomas I am aware of all of that, I agree with you and your answer actually answers the specific question. The only thing that tickles me is that the OP might get the impression, that this is how you generally solve this problem. In this particular example, it's totally fine. But would there be more than just incrementing and outputting a counter, we would very probably shoot ourselves in the foot. So I would have liked to read at least a mention of this fact. – Fildor Jul 06 '16 at 13:00