I must say this is troublesome. As for now I understand the following:
1. We should put volatile before variables which can be accessed from multiple threads. But how to know this? Should we look at the run() method only?
2. We can put volatile in front of each variable but this will eventually slow down the program (using slower memory).
3. No need to put volatile where's already 'final' modifier since this variable once set can't be changed anymore.
The "Diligence..." task took me 5 tries to solve it even if it's marked as easy. Why exactly we must place volatile in that place?
How to find the right place for volatile?
Dyskutowane
Komentarze (2)
- Popularne
- Najnowsze
- Najstarsze
Musisz się zalogować, aby dodać komentarz
Thomas
2 listopada 2023, 09:24przydatny
Yes, the run method is the 'task' and that's the code that concurrently runs. A Runnable constructor runs in the creating thread (main in most of the CG tasks).
Runnable task = new Runnable(someData);
The someData passed to the Runnable and the entire construction of the Runnable is happens from main. Then you create the Thread and start it. From that time on you have concurrent processes, the run method and your main method.
The compiler optimizes the code, same for CPUs. Both may not do what you expect. One point is the caching of which you may have read and that's a problem you solve with volatile (for fast execution data may be cached [copied to registers] and that cached versions used in loops [the run loop]). Volatile refreshes the cache (not only with marked value but all data gets updated).
Another point is code reorganization. The compiler may reorganize code. If you wrote eg. statement a; statment b; then the compiler may change execution to b; a; (if that does not change the behavior of your code). But it may be a problem for other tasks (have a look at the JLS chapter 17 for an example). When a is synchronized (or volatile), then the compiler makes sure it happens before b.
Some easy rules.
1. Synchronization is only for heap memory and not for the stack (local variables and parameters)
Just keep in mind that when you have a reference as parameter, then the reference does not need synchronization but the object behind may need it.
2. volatile may only be needed for primitives (and references but not objects)
3. Cause of 1. we can say only instance variables, static variables and objects need synchronization.
4. If there are no writes, then no synchronization is needed (as you said final makes you sure about that).
5. If there are writes they need to be atomic (like if you change a boolean, that's done in a single operation or if you change an int from 0 to 1, but not incrementing an int i++, that's internally three operations).
+1
Thomas
2 listopada 2023, 09:49
6. volatile and changing references: this applies just to immutable objects. So if the reference changes, we need to make sure the task knows about the change. The object behind is not important, eg.
public static volatile String stopped = "false"; // reference points to "false"
stopped = "true"; // reference changes and points to a new object "true". The object "false" did not change. As soon as the object may change as well, you need other forms of synchronization.
The CG task:
Inside this Runnable you have an instance variable variable throwable. As said above, instance variables may need a way of synchronization. And that variable is used inside the run method (the part of the Runnable that is running in a spawned thread).
Thread 1: When the task runs it may throw an exception and then save the exception in the throwable variable.
Thread 2: in the runTaskBySchedule() method, after the task has been started they confuse you with an ExecutorService that does nothing (interrupting the previous thread without that to listen to the interrupt). Then the main thread sleeps and then it accesses the Runnable object (using the main thread) via the rethrow() method. The rethrow method again access the throwable instance variable. throwable is assigned a new object, the reference changes.
So you have accesses from the created thread and the main thread to the throwable instance variable and an atomic write happens from the main thread, the reference changes to another object -> volatile.
0