0

I am familiar with the synchronized block/method. Is this required when modifying fields from separate threads? Will setting and getting the field without synchronization at the same time cause the field to get screwed up? Here is a little graph:

Time | Thread A    | Thread B
1      ...           ...
2      fieldA = 10;  System.out.println(fieldA);
3      fieldA = 20;  fieldA = 30;
4      ...           ...

Could something like this cause some sort of exception and multi-threaded fields should always be accessed through synchronization? Could this behavior change when using objects vs. primitives as a field type?

Reading this, it convinces me that two threads have different memory and each field change is cloned to all the other threads "sharing" this field.

Could bytecode instructions be executed in strange ways? Like say the bytecode to do ++ was getting the value, incrementing the value, and storing it.

Time | Thread A    | Thread B //Imagine in bytecode
1      x = 10        waiting
2      y = x + 1     waiting
3      waiting       x = 0;
4      waiting       ...
5      x = y         waiting

In the above example, Thread A incremented x from 10 to 11, but Thread B's x = 0 was completely ignored. If this type of thread switching didn't happen and it was done on a statment-by-statement basis, then the result of the two threads work would result either 0, or 1.

So again, my question is, when is synchronization required when modifying shared fields? If synchronization is required or not, can you please explain why and include the few examples above?

Community
  • 1
  • 1
Chris Smith
  • 2,928
  • 4
  • 27
  • 59
  • https://en.wikipedia.org/wiki/Race_condition May be of some help in classifying race conditions. – CollinD Oct 20 '15 at 21:13
  • @CollinD This is exactly what I am talking about. I would kind of expect Java would have something as simple as this built in. But wouldn't expect it to be in lower level languages such as C or even assembly at all. – Chris Smith Oct 20 '15 at 21:17
  • 1
    It does have a built-in feature, synchronized blocks! – CollinD Oct 20 '15 at 21:18
  • It also has volatile, AtomicInteger, etc. – JB Nizet Oct 20 '15 at 21:19
  • I mean for fields, there would be no intension for a field to be set at the same time by a programmer, I would sort of expect it be a built in feature. – Chris Smith Oct 20 '15 at 21:20
  • 1
    @chris13524 if it was built-in, the performance of Java would decrease significantly, although 99.99% of a typical program reads and modifies fields of an object from a single thread, making this performance degradation completely useless. Most of the time, stateful objects are not shared between threads. Frameworks are designed to avoid that as much as possible. When they are shared, then you must care about synchronization. – JB Nizet Oct 20 '15 at 21:23

1 Answers1

2

Yes -- this is pretty much a textbook example of things you have to synchronize.

If you want to get into the JLS, chapter 17 has all the relevant stuff. Some excerpts:

Two accesses to (reads of or writes to) the same variable are said to be conflicting if at least one of the accesses is a write.

...

When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race.

...

A set of actions is sequentially consistent if all actions occur in a total order (the execution order) that is consistent with program order

...

If a program has no data races, then all executions of the program will appear to be sequentially consistent.

In other words:

  • if you have reads and writes to the same field across threads, and one of those is a write, then you have a data race unless you synchronize access
  • if you have a data race, pretty much all bets are off

It's even weirder than the cases you provide. You can have code a = 10; b = 20;, and one thread will see execution in that order, while another thread sees b = 20; a = 10;. This happens due to optimizer reorderings, memory caches, and any number of optimizations that make your multi-core machine go fast, but at the cost of confusing multi-threaded behavior.

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • 1
    Not only that, but ++ is not an atomic operation for example, and simply affecting a new value to a double or long variable isn't necessarily either. – JB Nizet Oct 20 '15 at 21:18
  • Good point! Basically, all _sorts_ of things go wrong. It's probably a shorter list to describe what you can expect to go _right_, rather than list all the things that can go wrong. – yshavit Oct 20 '15 at 21:25
  • @chris13524 `foo++` is not a single action. It's three actions: read `foo` (which is atomic _on its own_) into a frame-local (and thus thread-local var); increment that var; write that var to `foo` (that write is also atomic _on its own_). But other things can come in and happen between those three events. – yshavit Oct 20 '15 at 21:27