so we went through some of the drawbacks to using synchronized blocks and I
talked just at the end of the last video about another way of using or
preventing thread interference and that's by using classes that implement
the java.util.concurrent.locks lock interface
so this package contains several implementations of the lock interface
that we can use
and as we're about to see the lock implementations that java provide don't
have the drawbacks that
I outlined in the previous video about that synchronized blocks have so let's
change our application now to use a lock object we use an instance of the
reentrant lock class so let's go have a look at that we're going to close this
down so I'm going to add a line just after the buffer in our main method
re-entrant lock to call this one buffer lock equals....
now you can probably guess by the name that this lock is reentrant if its
thread is already holding a reentrant lock when it reaches the code that requires
the same lock it can continue executing it doesn't have to obtain the lock again
now that might sound as if it should be a given but to in fact not all
implementations of the lock interface are re-entrant so we are going to create the lock
instances as I have done their on line 15 and we're going to share that with the
producer and the consumer remember that all the threads have to be competing for
the same lock to prevent thread interference
i'm going to create an instance of that in the main method as you saw me do and
we have to pass the lock to the producer and consumer instances
let's do that as well....
....
let's then change the classes that the producer and the consumer
classes we want to add a private...
so that so we can store that in our class will change the constructor...
....
....the next let's look at our run method
so we're going to do first is take out the synchronize blocks that we
previously added or we added in the previous video
....
ok so how do we actually use this buffer lock it turns out it's quite
simple so we just come up to the code above where we had the synchronized code
and we just type in buffer lock
....then execute the buffer.add
then we do....then we'll come down and do exactly the same
thing in the end of file after the system that out to print line...
...so very similar to the synchronized block in that we are
surrounding the code that we ultimately want to be thread-safe to avoid thread
interference with .lock and .unlock
that's for the code for the producer class and just want to clarify something
here that we're using the lock method to acquire the lock and the unlock method
to release the lock but this is very important when using lock objects
we're responsible for releasing the lock it won't happen automatically it doesn't
happen automatically as it would when we use the synchronized block
so the intrinsic locks we use with synchronized blocks are always released
when the thread holding the lock exits the synchronized block
so this is one drawback to using lock objects in that we have to manage this
lock process themselves by making sure we call the .lock and the unlock
methods now when a thread calls the lock method it will either obtain the lock
and continue executing or it won't be able to get the lock because of thread
already has it
so in that case the thread that called lock will be suspended until it can get that
lock
if we forget to release the lock threads waiting for the lock will then start
blocking forever
so in our producers as you saw the code I've just changed their we get the lock
before we added a number string after adding the string we then release the
lock and the same goes for when we get the end of file indicator so lets go
and do similar change now to our consumer class and we need to add our private...
....
instead we're going to do after the while true
you guessed it buffer....and write down at the end after the last
statement has been executed...
so you've seen here we've essentially replace the synchronized statement with
a call to lock and replace the end of the synchronized block with a call to
unlock
so let's run and see what happens and we can see that there's a problem and this
could depend on timing because you might find this all sort of running on a
little bit further but in this case it's done as a suspected it would
the producer adds the first string you can see that the output is indicated that
happen then nothing happens for quite a while and then eventually I will leave it long
enough we should probably get a maximum lock count exceeded message with an
exception and may or may not come out
come on but the bottom there it is there now so does take some time but what
happened and i'll just move that over so you can see that message a bit more
maximum lock count exceeded so clearly we've got a problem with this code as it
stands
so what actually going on at this point
well we got the consumer code on the screen
now looking at the produce lock code again it seems to be pretty well okay
because we got a lock and then were performing that one line of code
and then unlocking we're doing that
obviously twice once to add the numbers and once to add the end of file
indicator but in the consumer code come down there and have a look at that we
enter the loop and then we get the lock the lock is the first thing we do after
entering the loop
that's on line 74 we then check to see if there's any data in the buffer
if there is data we continue executing the loop code if there isn't
we loop back and get the lock again because we're still holding the lock
so you can you see what the problem is here we've never released the lock as
such
so the buffer lock.unlock code is never executed because that's only the
moment written on line 84 after the while loop exits
when we were using a synchronized block the lock was released because we exited
the synchronized block when we return to the beginning of the while loop by just
exiting the synchronized block that automatically released the lock but when we
are using the lock object ourselves we skip over the unlock code
and keep getting the lock again and again the problem here is this line 76
we are continuing which essentially is in calling this unlock code
so re-entrant lock objects can keep track of how many times they locked if the
thread gets the same lock twice it has to release it twice before another
thread can get the lock and i just want to give you a pseudo example of what I
mean here we are just going to paste is this code in but we won't need to do
anything with it just so you can get an idea of what I'm talking about
so there's an example of what I'm talking about so when method B calls
unlock
we wouldn't want any other threads to be able to get lock one because method
a still expect to be holding the lock surrounded a section of code with lock
and unlock because the code itself is a critical section now because the same
thread can acquire the same lock multiple times
the unlock method won't actually release the lock until the lock count
also known as also called the hold count reaches zero so in our consumer
case we get the lock so many times that eventually we exceed the maximum lock
count allowed
also since we never release the lock the producer is never able to add more data
and that's why you saw it sitting there sort of frozen after adding one item so
just delete the code so clearly we need to add more unlock statements to our
consumer class
we need one when the buffer is empty and we continue the loop but we also need
when we read end of file and break out of the loop as well so let's add those
statements again the first one here is here after the test to see whether it's
empty before the continued
we're going to do a buffer lock....and also down here after the break before
the break I should say we do a buffer....
let's just run that to see what happens so this you can see that we
were successful application is behaving again
so you might be thinking that you know this could get messy and perhaps you
should be sticking with synchronized blocks we could have unlocked statement
scattered throughout our code
so our code and in doing that potentially could be more error-prone and difficult
to maintain
but that's because we're not using locks in the recommended way so i wanted to
show you this so that we can see why we always want to enclose the critical
sections of code with a try finally block but let's talk more about that in
the next video