Home > other >  Java multithreading unexpected output
Java multithreading unexpected output

Time:08-31

I am trying to learn multithreading in Java. Here is the code that I wrote:

public class SharedResources {
    volatile private int sharedPar1;
    volatile private String sharedPar2;

    public SharedResources(Integer sharedPar1, String sharedPar2) {
        synchronized (sharedPar1) {
            this.sharedPar1 = sharedPar1;
        }
        synchronized (sharedPar2) {
            this.sharedPar2 = sharedPar2;
        }
    }

    public synchronized int getSharedPar1() {
        return sharedPar1;
    }

    public synchronized void setSharedPar1(int sharedPar1) {
        this.sharedPar1 = sharedPar1;
    }

    public synchronized String getSharedPar2() {
        return sharedPar2;
    }

    public synchronized void setSharedPar2(String sharedPar2) {
        this.sharedPar2 = sharedPar2;
    }
}

This above is simply one class that represents mock shared resources. Two mock thread classes:

public class Task1 extends Thread {
    volatile private SharedResources sharedResources;

    public Task1(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    public SharedResources getSharedResources() {
        return sharedResources;
    }

    public void setSharedResources(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i  ) {
            sharedResources.setSharedPar1(sharedResources.getSharedPar1()   1000);
            sharedResources.setSharedPar2(sharedResources.getSharedPar2()   "c");
            System.out.println("Task1, it. "   i   ". - Shared resources: "   sharedResources.getSharedPar1()   " and "   sharedResources.getSharedPar2());
        }
    }
}

Task 2 :

public class Task2 extends Thread {
    volatile private SharedResources sharedResources;

    public Task2(SharedResources sharedResource) {
        this.sharedResources = sharedResource;
    }

    public SharedResources getSharedResources() {
        return sharedResources;
    }

    public void setSharedResources(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i  ) {
            sharedResources.setSharedPar1(sharedResources.getSharedPar1()   100);
            sharedResources.setSharedPar2(sharedResources.getSharedPar2()   "b");
            System.out.println("Task2, it. "   i   ". - Shared resources: "   sharedResources.getSharedPar1()   " and "   sharedResources.getSharedPar2());
        }
    }
}

Now, the Main function:

public static void main(String[] args) {
        SharedResources sharedResource = new SharedResources(0, "");

        Thread firstTask = new Task1(sharedResource);
        Thread secondTask = new Task2(sharedResource);

        firstTask.start();
        secondTask.start();

        for (int i = 0; i < 10; i  ) {
            System.out.println("Main thread! - Shared resources: "   sharedResource.getSharedPar1()   " and "   sharedResource.getSharedPar2());
            sharedResource.setSharedPar1(sharedResource.getSharedPar1()   1);
            sharedResource.setSharedPar2(sharedResource.getSharedPar2()   "a");
        }
        System.out.println();
    }

Now, idea is this - having two threads that make different operations on variables sharedPar1 and sharedPar2, in output I want to get consistent values of variables, meaning I want to be able to logically connect output of this program, to its lines of code. But, I don't know why there are some things as they are in my output. Basically, I need someone to explain this kind of output to me:

Main thread! - Shared resources: 1000 and 
Main thread! - Shared resources: 1101 and ba
Main thread! - Shared resources: 1102 and baa
Main thread! - Shared resources: 1103 and baaa
Main thread! - Shared resources: 1104 and baaaa
Main thread! - Shared resources: 1105 and baaaaa
Main thread! - Shared resources: 1106 and baaaaaa
Main thread! - Shared resources: 1107 and baaaaaaa
Main thread! - Shared resources: 1108 and baaaaaaaa
Main thread! - Shared resources: 1109 and baaaaaaaaa

Task2, it. 0. - Shared resources: 1100 and b
Task1, it. 0. - Shared resources: 1100 and b
Task2, it. 1. - Shared resources: 1210 and baaaaaaaaaab
Task1, it. 1. - Shared resources: 2210 and baaaaaaaaaabc
Task1, it. 2. - Shared resources: 3310 and baaaaaaaaaabcc
Task2, it. 2. - Shared resources: 3310 and baaaaaaaaaabcb
Task1, it. 3. - Shared resources: 4310 and baaaaaaaaaabcbc
Task2, it. 3. - Shared resources: 4410 and baaaaaaaaaabcbcb
Task1, it. 4. - Shared resources: 5410 and baaaaaaaaaabcbcbc
Task2, it. 4. - Shared resources: 5510 and baaaaaaaaaabcbcbcb
Task1, it. 5. - Shared resources: 6510 and baaaaaaaaaabcbcbcbc
Task2, it. 5. - Shared resources: 6610 and baaaaaaaaaabcbcbcbcb
Task1, it. 6. - Shared resources: 7610 and baaaaaaaaaabcbcbcbcbc
Task2, it. 6. - Shared resources: 7710 and baaaaaaaaaabcbcbcbcbcb
Task1, it. 7. - Shared resources: 8710 and baaaaaaaaaabcbcbcbcbcbc
Task1, it. 8. - Shared resources: 9810 and baaaaaaaaaabcbcbcbcbcbcc
Task1, it. 9. - Shared resources: 10810 and baaaaaaaaaabcbcbcbcbcbccc
Task2, it. 7. - Shared resources: 9810 and baaaaaaaaaabcbcbcbcbcbcb
Task2, it. 8. - Shared resources: 10910 and baaaaaaaaaabcbcbcbcbcbcccb
Task2, it. 9. - Shared resources: 11010 and baaaaaaaaaabcbcbcbcbcbcccbb

I understand everything in the output until line Task2, it. 0. - Shared resources: 1100 and b - why in this line we do not have values from previous line (last line in Main loop)? Output of main, where we have 'aaa..' added to string and ones added to integer variable are nowhere to be found in this line, and that is what I do not understand.

So, basically I want to be able to use shared class and variables between multiple different threads, but even if I have set variables to 'volatile' and methods to 'synchronized', again values are not properly updated when outputted to the screen. Can someone explain why is that? And if you see that this is wrong way to do these kind of things, where am I wrong?

Also, I am wondering if there is a way to ensure that in one thread, we always execute lets say one full iteration of a loop before scheduler kicks our thread out of processor?

CodePudding user response:

There are several issues. Firstly you have a race condition on the get/set cycles so that each thread may read the same initial value and therefore they replace an updated value of another thread. This is caused as you do not synchronise on this entire block for atomic change to each independent variable and two threads perform getSharedParX() before each runs sharedResource.setSharedParX(updatedValue):

sharedResource.setSharedParX(sharedResource.getSharedParX()   x);

The timeline of your read/updates is therefore trampling the results of other threads (note that main prints before it updates), and first line of output of these threads use a mix of the first/second values of sharedPar1 and sharedPar2:

// Task1 read sharedPar1=0 and saved sharedPar1=1000
Main thread! - Shared resources: 1000 and 

// Task2 reads sharedPar1=1000 and saved sharedPar1=1100 (trampling main values)
// Task2 read sharedPar2="" and saved sharedPar2="b" (trampling main/Task2 values)
Task2, it. 0. - Shared resources: 1100 and b
Task1, it. 0. - Shared resources: 1100 and b

Secondly note that System.out is a PrintWriter so the println messages are synchronized. Main thread was simply able to print more results before Task1/Task2 had a chance to, which is why the first lines from Task1/2 appear well down the list. It's generally not a good idea to print to same stream inside threads as the I/O contention could slow down all the threads as they contend with each other to write output.

You need to fix so that the operations are atomic - look at using AtomicInteger or AtomicReference<String> or implement your own handlers to perform the changes needed in one step (and remove setX) such as:

synchronized int addAndGet(int delta) {
    sharedPar1  = delta;
    return sharedPar1;
}
synchronized String appendAndGet(String delta) {
    sharedPar2  = delta;
    return sharedPar2;
}
  • Related