in this lecture we are going to cover a few loose ends related to threads that
didn't sort of quite fit into the previous lectures
firstly let's talk about what an atomic action means or remind ourselves what
that is
as we've seen in the section when a threads running it can be suspended when
it's in the middle of doing something for example if a thread calls the system.out
.println method it can be suspended in the middle of executing the
method it may have evaluated the argument that's been passed but it's
suspended before it can print the result
alternatively it might be part way through evaluating the argument when
it's suspended so essentially system.out.println isn't an atomic
action so an atomic action can't be suspended in the middle of being
executed it either completes or it doesn't happen at all once the thread
start to run an atomic action we can be confident that it won't be suspended
until it is completed that action now in Java we mentioned that the following
operations are atomic number one reading and writing reference variables for
example the statement my object 1 equals my object 2 would be atomic a thread
can't be suspended in the middle of executing this statement number two
reading and writing primitive variables except those of type long and double the
jvm may required 2 operations to read and write longs and doubles and a thread can
be suspended between each operation for example a thread can't be suspended in
the middle of executing my int = 10 but it could be suspended in the middle
of executing my double =1.234 and number three reading and
writing all variables declared volatile
so let's take a look at this third item volatile variables you may think that
since we don't have to worry about thread interference with atomic actions
that we don't need to synchronize them but that isn't true because of the way
that java managers memory it's possible to get memory inconsistency errors when
multiple threads can read and write the same variable javas memory management
model is outside the scope of this lecture all we need to really know here
though is that each thread has a cpu cache which can contain copies of values
that are in the main memory
since it's faster to read from the cache this can improve the performance of an
application now this wouldn't be a problem if there's only one cpu but
these days most computers have more than one cpu when running an application
each thread may be running on a different cpu and each CPU has its own
cache therefore it's possible for the values in the caches to become out of
sync with each other and with the value in main memory therefore getting a
memory consistency error
and as an example let's suppose we've got two threads that use the same
int counter now thread one reads and writes the counter thread 2 only
reads the counter
as we know reading and writing to an int isn't an atomic action a thread won't be
suspended in the middle of reading or writing the value to memory but let's
suppose that thread one is running on CPU 1 and thread 2 is running on CPU 2
because of cpu caching the following can happen one the value from the
counter is zero in main memory 2 thread one reads the value of zero from main
memory 3 thread one adds one to the value 4 thread one writes the value of
1 to its CPU cache 5 thread 2 reads the value of counter from main memory and get zero
rather than the latest value which is one
this is where volatile variables come in when we use a nonvolatile variable the
jvm doesn't guarantee when it writes an updated value back to main memory but
when we use a volatile variable the jvm writes the value back to main memory
immediately after thread updates the value in its CPU cache and it also
guarantees that every time a variable reads from a volatile variable it will
get the latest value so to make a variable volatile we simply use the
volatile keyword as you can see on the image below
you might be thinking that we don't have to synchronize code that uses
volatile variables
unfortunately that's not entirely true in our example only one thread is
updating the variable in that case we wouldn't need synchronization but if more
than one thread can update the value of a volatile variable we can still get a
race condition so lets assume that we have two threads that share a volatile
int counter and each thread can run the following code counter ++ as you
can see in the image
now this or that wasn't an atomic operation so with thread would have to do the
following 1 read the value of counter for memory 2 add one to counter then
3 write the new value of counter back to memory
now a thread can be suspended after any of these steps as a result of that the
following can actually happen
1 the value of the counter is one in main memory in thread 1 and thread 2 cpu
caches 2 thread one reads the value of counter and gets 1 3 thread 2 reads the value of
counter and also get 1 4 thread one increments the value and gets 2 and
then it writes
2 to its cache the jvm immediately writes 2 to main memory thread 2 increment
the value and gets 2 it writes 2 to its case the jvm immediately writes 2
to main memory and 6 oops at this point because the counter has been
incremented twice so its value should now be three
a memory consistency error like this can occur when a thread can update the value
of a variable in a way that depends on the existing value of that variable in
the counter + + case the result of the increment depends on the existing value
of the variable in other words the thread has to read the value of the
counter variable in order to generate a new value for counter by the time the
thread operates on the value its read the value could be stale as it was in
the example that I've just highlighted
so whether to synchronize when using a volatile variable will depend on the
code and what the thread we'll be doing now common use of volatile is
with variables of type long and double reading and writing longs and doubles isn't
an atomic as I've mentioned using volatile makes them atomic but i would still have
to use synchronization in the situation we just described when the thread reads
the value of a variable and then uses the value which maybe stale to generate
a new value for the variable
however when only one thread can change the value of a shared variable or none
of the threads update the value of a shared variable in a way that depends on
its existing value then using the volatile keyword does mean that we don't
need to synchronize the code we can be confident that the value in main memory
is always the latest value that's great and we can see that there will be times
when using the volatile keyword will eliminate the need for synchronization
but it would be nice if we could read and write variables without having to
worry about thread interference or memory consistency errors
now fortunately Java does provide classes that allow us to do just that in
specific cases
in an earlier lecture we took a look at a few of the classes in the java.util
.concurrency package which was introduced with java 1.5
now there's another sub package called java.util.concurrent.atomic
that package provides us with classes that we can use to ensure that reading
and writing variables is atomic now it's difficult to create an example that
illustrates how the classes in the package are useful like most thread
concepts the benefits are seen when there are multiple threads performing
non trivial tasks so going to have a look at some code but we're not actually
going to run anything
starting off lets suppose we've got multiple threads using the following
counter class so you can see that we've got a private int there
this class counter called counter and we've got a couple of methods as you can
see there and I have also got a getter that returns the value of the counter so the
public method inc increments the counter and the public method dec decrements
the counter variable value
we've learned enough now to know that their would be the potential for thread
interference because the increment and decrements operations aren't atomic so
once again an increment involves the following steps number one read the
value of counter number 2 add one to that value number 3 write the new
value back to counter
a thread can be suspended between any of those three steps and as a result the
following could happen
number one thread one reads the value of counter and gets five number 2 thread
one spends number 3 thread 2 read the value of counter and gets five it
adds one to the value and write six to the counter and suspense 4 thread
three reads the value of counter and get six it adds one to the value and writes 7 to
the counter and suspends thread one runs again
it adds one to the value of five and then write six to counter and step 6
well basically oops we're actually in a bad position at that point in time
of course we can't come up with more runs sequences that would lead to the
value of counter being compromised as well
declaring the counter variable as volatile as one potential solution but
we've learned that memory consistency errors can still be possible which would
be true here for the increment and or the inc and dec methods that
increment and decrements the value now since the thread changes the value of counter
in a way that depends on the existing value of counter the values within the
thread cpu caches make it out of sync with the value in main memory
now instead of using a counter we are going to update the code to use an atomic
integer object when using one of the atomic types we don't have to worry about
thread interference now as the java documentation states the classes in the
java.util.concurrent.atomic package support lock-free thread-safe
programming on single variables and I've got a link to the documentation there and
that will be in the resources section for this video so we declare the
counter as type atomic integer as you can see in the example below we passed 0 as
the initial value now in the inc method we use increment and get this atomically
increases the value by one now the method decrementing get decreases the
value of by 1 to get the value we call the get method so the advantage of this is
we don't have to synchronize the increment or decrement operations in
anyway that's obviously using that to revise using that atomic
package that i mentioned on the previous slide we can't use atomic integer as a
substitute for an integer object atomic integers are meant to be used in specific
situations like the one mentioned on the previous slide where thread interference
can take place because of threads changing the value of the variable in a
way that depends on the existing value if you take a look at the documentation
for the atomic integer class you'll see that there are a limited
number of methods and most allow us to operate on the existing value of the
atomic variable and as before there's a link in the resources section of this video
where you can actually go and check that your leisure
there are atomic classes for the following types Boolean integer integer
array long long array
object reference and double now remember that I said reading and writing long
and double variables isn't atomic well we could use the atomic long and atomic
double classes to make those operations atomic the atomic classes have a set
and get methods that allow us to set of value and to get the current
value but the atomic classes are really meant to be used in situations where
value is being incremented or decremented they're really intended to
be used used when the code is using a loop counter for generating a sequence
of numbers for some other reason
and lastly one other method will briefly mention for the atomic classes is the
compare and set method this method takes two parameters the expected value and
the new value that you want to set if the current value doesn't equal the
expected value the method returns false and the set then doesn't take place but if
the current value equals the expected value than the set goes ahead and the
method returns true and if you think about it you can probably see how this
would be really useful when a thread knows that might be suspended between
getting a value and updating it
alright so that's the end of this video and the next one will go back to writing
some code on threads so we'll see you in that video