5
public class MyStack2 {
    private int[] values = new int[10];
    private int index = 0;

    public synchronized void push(int x) {
        if (index <= 9) {
            values[index] = x;
            Thread.yield();
            index++;
        }
    }

    public synchronized int pop() {
        if (index > 0) {
            index--;
            return values[index];
        } else {
            return -1;
        }
    }

    public synchronized String toString() {
        String reply = "";
        for (int i = 0; i < values.length; i++) {
            reply += values[i] + " ";
        }
        return reply;
    }
}

public class Pusher extends Thread {
    private MyStack2 stack;

    public Pusher(MyStack2 stack) {
        this.stack = stack;
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            stack.push(i);
        }
    }
}

public class Test {
    public static void main(String args[]) {
        MyStack2 stack = new MyStack2();
        Pusher one = new Pusher(stack);
        Pusher two = new Pusher(stack);
        one.start();
        two.start();
        try {
            one.join();
            two.join();
        } catch (InterruptedException e) {
        }
        System.out.println(stack.toString());
    }
}

Since the methods of MyStack2 class are synchronised, I was expecting the output as 1 2 3 4 5 1 2 3 4 5. But the output is indeterminate. Often it gives : 1 1 2 2 3 3 4 4 5 5

As per my understanding, when thread one is started it acquires a lock on the push method. Inside push() thread one yields for sometime. But does it release the lock when yield() is called? Now when thread two is started, would thread two acquire a lock before thread one completes execution? Can someone explain when does thread one release the lock on stack object?

sampathsris
  • 21,564
  • 12
  • 71
  • 98
sam
  • 75
  • 4
  • Take a look at this http://stackoverflow.com/questions/18635616/yield-inside-synchronized-block-lock-release-after-calling-yield – Alex Oct 07 '14 at 13:57

3 Answers3

5

A synchronized method will only stop other threads from executing it while it is being executed. As soon as it returns other threads can (and often will immediately) get access.

The scenario to get your 1 1 2 2 ... could be:

  1. Thread 1 calls push(1) and is allowed in.
  2. Thread 2 calls push(1) and is blocked while Thread 1 is using it.
  3. Thread 1 exits push(1).
  4. Thread 2 gains access to push and pushes 1 but at the same time Thread 1 calls push(2).

Result 1 1 2 - you can clearly see how it continues.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • Yeah but if that is the case the output of the program should not be like : 1 1 2 2 3 3 4 4 5 5. But often this is the output. – sam Oct 07 '14 at 14:13
  • 7
    @javaTech - There is no contract that says that Java Threads should be fair. Almost any order could be the result of your code. 1 1 2 2 ... is quite valid. – OldCurmudgeon Oct 07 '14 at 14:20
2

When you say:

As per my understanding, when thread one is started it acquires a lock on the push method.

that is not quite right, in that the lock isn't just on the push method. The lock that the push method uses is on the instance of MyStack2 that push is called on. The methods pop and toString use the same lock as push. When a thread calls any of these methods on an object, it has to wait until it can acquire the lock. A thread in the middle of calling push will block another thread from calling pop. The threads are calling different methods to access the same data structure, using the same lock for all the methods that access the structure prevents the threads from accessing the data structure concurrently.

Once a thread gives up the lock on exiting a synchronized method the scheduler decides which thread gets the lock next. Your threads are acquiring locks and letting them go multiple times, every time a lock is released there is a decision for the scheduler to make. You can't make any assumptions about which will get picked, it can be any of them. Output from multiple threads is typically jumbled up.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 1
    "the locking prevents the data structure from being accessed concurrently." Careful! Newbies may not understand that it is not the locking _per se_ that protects the data. They need to be told (more than once sometimes) that what protects the data is the fact that _every block of code_ that updates or uses the data locks the same lock. – Solomon Slow Oct 07 '14 at 18:44
  • @james: agreed, reworded. – Nathan Hughes Oct 07 '14 at 18:47
1

It seems like you may have some confusion on exactly what the synchronized and yield keywords mean.

Synchronized means that only one thread can enter that code block at a time. Imagine it as a gate and you need a key to get through. Each thread as it enters takes the only key, and returns it when they are done. This allows the next thread to get the key and execute the code inside. It doesn't matter how long they are in the synchronized method, only one thread can enter at a time.

Yield suggests (and yes its only a suggestion) to the compiler that the current thread can give up its allotted time and another thread can begin execution. It doesn't always happen that way, however.

In your code, even though the current thread suggest to the compiler that it can give up its execution time, it still holds the key to the synchronized methods, and therefore the new thread cannot enter.

The unpredictable behavior comes from the yield not giving up the execution time as you predicted.

Hope that helped!

Jared Wadsworth
  • 839
  • 6
  • 15
  • "Synchronized means that only one thread can enter that code block at a time." That is only true if the code always tries to lock the _same object_ each time it enters the block. More than one thread can enter the same `synchronized(foo){...}` block if `foo` is not a constant. – Solomon Slow Oct 07 '14 at 18:48
  • "Yield suggests...to the compiler that..." Not to the compiler. The compiler doesn't know the difference between Thread.yield() and any other method call. The magic (if any) happens at run-time, most likely when the native thread in the JVM implementation makes some kind of a "yield" system call. – Solomon Slow Oct 07 '14 at 21:44
  • 2
    "The unpredictable behavior comes from the yield not giving up..." I don't think so because, as you pointed out, the yield() call happens inside a synchronized block. Regardless of whether it does anything or not, the other thread will not run before yield() returns. The unpredictability comes from the fact that there's two threads repeatedly contending for the same lock. Suppose thread A has the lock, and thread B is waiting for the lock. When thread A releases the lock and then _immediately_ tries to lock it again, which thread gets the lock? _That_ is what is not predictable. – Solomon Slow Oct 07 '14 at 21:48