Skip to content

Commit

Permalink
Explain more about volatile
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewcmyers committed Nov 22, 2024
1 parent 9fd47db commit ce1fada
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 12 deletions.
45 changes: 33 additions & 12 deletions lectures/concurrency/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,23 +213,27 @@ <h2>Amdahl's Law</h2>
maximum speedup is
\[1 \over (1-p) + p/c\]
which, no matter how large \(c\) becomes,
never exceeds \(1/(1-p)\).
never exceeds \(1/(1-p)\). Here, the denominator term \(1-p\) represents the work
that cannot be parallelized, and the term \(p/c\) represents the parallelizable work,
sped up by a factor of \(c\).
</p>
<p>
Since most programs have some work that is difficult to parallelize, Amdahl's law explains
why it is often difficult to get large gains from having many cores.
Most programs have some work that is difficult to parallelize: often there is
some initial setup of computation or final collection of results that cannot be
done concurrently. Thus, Amdahl's law explains why it is often difficult to
get large gains from having many cores.
</p>

<h2>Race conditions</h2>
<p>
We have to be careful about multiple threads accessing the same object,
because threads can interfere with each other.
Two threads simultaneously reading information from the same object
is not a problem. Read-read sharing is safe. But if both threads are
is not a problem. Readread sharing is safe. But if both threads are
simultaneously trying to update the same object, or if one thread
is updating the object while the other is reading it, it is possible that
inconsistencies can arise. This is called a <strong>race condition</strong>.
Both read-write and write-write races are problems.
Both readwrite and writewrite races are problems.
</p>
<p>
For example, consider the following bank account simulation:
Expand Down Expand Up @@ -355,10 +359,10 @@ <h2>Mutexes and <code>synchronized</code></h2>
most one thread at a time.
</p>
<p>
Threads can <strong>acquire</strong> them and <strong>release</strong> mutexes.
Threads can <strong>acquire</strong> and <strong>release</strong> mutexes.
</p>
<p>
<b>acquire</b>: When a thread tries to acquire a mutex, it first checks whether
<dl>
<dt>acquire:<dd><p>When a thread tries to acquire a mutex, it first checks whether
the mutex is already held by another thread. If so, the thread blocks
and makes no further progress, until some future time when the mutex is no
longer held by any thread. At that point, the thread then becomes the unique
Expand All @@ -368,13 +372,14 @@ <h2>Mutexes and <code>synchronized</code></h2>
implementations usually try to provide some <strong>fairness</strong>, so that threads
do eventually acquire the mutex.
</p>
<dt>release:<dd>
<p>
<b>release</b>:
At most one thread can hold a mutex at a time.
While a mutex is being held by a thread, all other threads that try to acquire
it will be blocked until it is released, at which point
a waiting thread, if any, will manage to acquire it.
</p>
</dl>
<p>
Considering the single-owner principle, we can think of a mutex as guarding
some mutable state. When no thread holds the mutex, no thread can access the
Expand Down Expand Up @@ -491,6 +496,21 @@ <h3>Volatile variables</h3>
all you want is indeed to acquire a mutex around all accesses to a variable;
in this case, <code>volatile</code> is the easiest and cheapest way to do it.
</p>
<p>
We might think that we could fix the <code>Account</code> example from earlier by
declaring <code>balance</code> to be volatile:
</p>

<pre>
<s>private volatile int balance;</s>
</pre>

<p>
But this declaration <em>will not help at all</em>. There are still two
separate accesses to the variable (a read and a write) from each of the methods of
<code>Account</code>. Each access will be separately synchronized, so all
interleavings remain possible.
</p>

<h3>Atomic abstractions</h3>
<p>
Expand All @@ -504,7 +524,7 @@ <h3>Atomic abstractions</h3>
standard Java mutexes.
</p>
<p>
For example, we could fix the <code>Account</code> example from earlier by
For example, we <em>could</em> fix the <code>Account</code> example from earlier by
storing the balance in an <code>AtomicInteger</code>:
<pre>
class Account {
Expand Down Expand Up @@ -535,7 +555,7 @@ <h2>When is synchronization needed?</h2>
Synchronization is needed whenever we must rely on invariants on
the state of objects, either between different fields of one or more
objects, or between contents of the same field at different times.
Without synchronization there is no guarantee that some other thread
Without synchronization, there is no guarantee that some other thread
won't be simultaneously modifying the fields in question, leading to
an inconsistent view of their contents.
</p><p>
Expand Down Expand Up @@ -591,5 +611,6 @@ <h2>When is synchronization needed?</h2>
</p><p>
Note that <strong>immutable</strong> state shared between threads doesn't need
to be locked because it cannot be updated. This fact
encourages a style of programming that avoids mutable state.
encourages a style of programming that avoids mutable state—which has an additional
benefit of simplifying reasoning about code even when concurrency is not being used.
</p>
2 changes: 2 additions & 0 deletions styles/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ p, li {

p.definition { font-weight: bold; padding: 1em; background-color: #ddd; border: 1px solid black }

dt { font-weight: bold }


@media print {
body {
Expand Down

0 comments on commit ce1fada

Please sign in to comment.