Synchronization on immutable objects (in java)

走远了吗. 提交于 2019-11-29 14:36:58
    synchronized (won)
    {
        if (!won)
        won = true;
    }

Here you have a transient race condition which you don't notice because it goes away after the first execution of the run method. After that the won variable constantly points to the same instance of Boolean representing true, which thus serves properly as a mutex lock.

This is not to say that you should ever write such code in real projects. All lock objects should be assigned to final variables to make sure they never change.

Whether or not an object is immutable has nothing to do with whether it's suitable as lock object in a synchronized statement. It is important, however, that the same object be used by all threads entering the same set of critical regions (hence it may be wise to make the object reference final), but the object itself can be modified without affecting it's "lockiness". Additionally, two (or more) different synchronized statements can use different reference variables and still be mutually exclusive, so long as the different reference variables all refer to the same object.

In the above examples the code in the critical region replaces one object with another, and that is a problem. The lock is on the object, not the reference, so changing the object is a no-no.

I was having a race condition with the first code snippet. I understood that this was because I was obtaining a lock on an immutable object(of type Integer).

Actually, that is not the reason at all. Obtaining the lock on an immutable object, will "work" just fine. The problem is that it probably won't do anything useful ...

The real reason that the first example breaks is that you are locking the wrong thing. When you do execute this - nRequests++ - what you are actually doing is equivalent to this non-atomic sequence:

    int temp = nRequests.integerValue();
    temp = temp + 1;
    nRequests = Integer.valueOf(temp);

In other words, you are assigning a different object reference the static variable nRequests.

The problem is that in your snippet the threads will be synchronizing on a different object each time an update is made to the variable. That's because each thread changes the reference to the object that is to be locked.

In order to synchronize properly, all of the threads need to lock the same object; e.g.

class RequestObject implements Runnable
{
    private static Integer nRequests = 0;
    private static final Object lock = new Object();

    @Override
    public void run()
    {       
        synchronized (lock)
        {
            nRequests++;
        }
    }
}

In fact, the second example suffers from the same problem as the first. The reason you are not noticing it in your testing is that the transition from won == false to won == true happens just once ... so the probability that the potential race condition will actually eventuate is much, much smaller.

In fact, your second code is also not thread safe. Please use the code below to check for yourself (you will find out that the first print statement will be 2 sometimes which means that there are two threads inside the synchronized block!). The bottom line: Code snippet - 1 & Code snippet - 2 are basically the same and thus are not thread safe...

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

public class Racer implements Runnable {
    public static AtomicInteger counter = new AtomicInteger(0);
    public static Boolean won = false;    

    @Override
    public void run() {
        synchronized (won) {
            System.out.println(counter.incrementAndGet()); //should be always 1; otherwise race condition
            if (!won) {
                won = true;
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(counter.decrementAndGet()); //should be always 0; otherwise race condition
        }
    }   

    public static void main(String[] args) {
        int numberOfThreads = 20;
        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);

        for(int i = 0; i < numberOfThreads; i++) {
            executor.execute(new Racer());
        }

        executor.shutdown();
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!