Proper use of volatile variables and synchronized

2019-05-06 17:14发布

I am trying to wrap my head around thread safety in java (or in general). I have this class (which I hope complies with the definition of a POJO) which also needs to be compatible with JPA providers:

    public class SomeClass {

        private Object timestampLock = new Object();
        // are "volatile"s necessary?
        private volatile java.sql.Timestamp timestamp;
        private volatile String timestampTimeZoneName;

        private volatile BigDecimal someValue;

        public ZonedDateTime getTimestamp() {
            // is synchronisation necessary here? is this the correct usage?
            synchronized (timestampLock) {
                return ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.of(timestampTimeZoneName));
            }
        }

        public void setTimestamp(ZonedDateTime dateTime) {
            // is this the correct usage?
            synchronized (timestampLock) {
                this.timestamp = java.sql.Timestamp.from(dateTime.toInstant());
                this.timestampTimeZoneName = dateTime.getZone().getId();
            }
        }

        // is synchronisation required?
        public BigDecimal getSomeValue() {
            return someValue;
        }

        // is synchronisation required?
        public void setSomeValue(BigDecimal val) {
            someValue = val;
        }
    }

As stated in the commented rows in the code, is it necessary to define timestamp and timestampTimeZoneName as volatile and are the synchronized blocks used as they should be? Or should I use only the synchronized blocks and not define timestamp and timestampTimeZoneName as volatile? A timestampTimeZoneName of a timestamp should not be erroneously matched with another timestamp's.

This link says

Reads and writes are atomic for all variables declared volatile (including long and double variables)

Should I understand that accesses to someValue in this code through the setter/getter are thread safe thanks to volatile definitions? If so, is there a better (I do not know what "better" might mean here) way to accomplish this?

3条回答
我只想做你的唯一
2楼-- · 2019-05-06 17:43
    // is synchronisation required?
    public BigDecimal getSomeValue() {
        return someValue;
    }

    // is synchronisation required?
    public void setSomeValue(BigDecimal val) {
        someValue = val;
    }

I think Yes you are require to put the synchronization block because consider an example in which one thread is setting the value and at the same time other thread is trying to read from getter method, like here in the example you will see the syncronization block.So, if you take your variable inside the method then you must require the synchronization block.

查看更多
做自己的国王
3楼-- · 2019-05-06 17:54

To determine if you need synchronized, try to imagine a place where you can have a context switch that would break your code.

In this case, if the context switch happens where I put the comment, then in getTimestamp() you're going to be reading different values from each timestamp type.

Also, although assignments are atomic, this expression java.sql.Timestamp.from(dateTime.toInstant()); certainly isn't, so you can get a context switch inbetween dateTime.toInstant() and the call to from. In short you definitely need the synchronized blocks.

synchronized (timestampLock) {
    this.timestamp = java.sql.Timestamp.from(dateTime.toInstant());
    //CONTEXT SWITCH HERE
    this.timestampTimeZoneName = dateTime.getZone().getId();
}

synchronized (timestampLock) {
    return ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.of(timestampTimeZoneName));
}

In terms of volatile, I'm pretty sure they're required. You have to guarantee that each thread definitely is getting the most updated version of a variable.

This is the contract of volatile. And although it may be covered by the synchronized block, and volatile not actually necessary here, it's good to write anyway. If the synchronized block does the job of volatile already, the VM won't do the guarantee twice. This means volatile won't cost you any more, and it's a very good flashing light that says to the programmer: "I'M USED IN MULTIPLE THREADS".


For someValue: If there's no synchronized block here, then volatile is definitely necessary. If you call a set in one thread, the other thread has no queue that tells it that may have been updated outside of this thread. So it may use an old and cached value. The JIT can do a lot of funny optimizations if it assumes single thread. Ones that can simply break your program.

Now I'm not entirely certain if synchronized is required here. My guess is no. I would add it anyway to be safe though. Or you can let java worry about the synchronization and use http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger.html

查看更多
甜甜的少女心
4楼-- · 2019-05-06 18:07

Nothing new here, this is just a more explicit version of something @Cruncher already said:

You need synchronized whenever it is important for two or more fields in your program to be consistent with one another. Suppose you have two parallel lists, and your code depends on them both being the same length. That's called an invariant as in, the two lists are invariably the same length.

How can you write a method, append(x,y), that adds a new pair of values to the lists without temporarily breaking the invariant? You can't. The method must add one item to the first list, breaking the invariant, and then add the other item to the second list, fixing it again. There's no other way.

In a single-threaded program, that temporary broken state is no problem because no other method can possibly use the lists while append(x,y) is running. That's no longer true in a multithreaded program. In the worst case, append(x,y) could add x to the x list, and then the scheduler could suspend the thread at that exact moment to allow other threads to run. The CPUs could execute millions of instructions before append(x,y) gets to finish the job and make the lists right again. During all of that time, other threads would see the broken invariant, and possibly corrupt your data or crash the program as a result.

The fix is for append(x,y) to be synchronized on some object, and (this is the important part), for every other method that uses the lists to be synchronized on the same object. Since only one thread can be synchronized on a given object at a given time, it will not be possible for any other thread to see the lists in an inconsistent state.

So, if thread A calls append(x,y), and thread B tries to look at the lists "at the same time", will thread B see the what the lists looked like before or after thread A did its work? That's called a data race. And with only the synchronization that I have described so far, there's no way to know which thread will win. All we've done so far is to guarantee one particular invariant.

If it matters which thread wins the race, then that means that there is some higher-level invariant that also needs protection. You will have to add more synchronization to protect that one too. "Thread safety" -- two little words to name a subject that is both broad and deep.

Good Luck, and Have Fun!

查看更多
登录 后发表回答