so as I mentioned in the last part of the last video by having to issue these
.lock and .unlock calls and have them scattered throughout the code it could
end up being quite messy so our code could be error prone and frankly
difficult to maintain
but the reason for that is because we're not using locks in the recommended way
want to show you this so that you can see why we always want to enclose the
critical sections of code within a try finally block now one reason for doing
so is that we only need to call unlock in one place
the other reason is that it would be possible for code within a critical
section to throw an exception that we're not explicitly handling using a catch
statement and we want to make sure that we have released any locks were
holding when that scenario plays out
so let's go ahead now and update the producer and the consumer to use the try
try finally block
firstly the producer code so I've got that on the screen now I'm going to come
down here after the lock and then the actual code
the buffer the code that basically the code that is in the
synchronization block or what would have been in the synchronization block
we're going to put a try around it...going to paste that code in the try block
we're not going to have a catch rather we are only going to have a finally
and I'm going to put the unlock in the finally clause likewise down here
after the .lock we're gonna put a try there as well
we'll put a finally and we'll add the code that updates the arraylist put
that in the try block and the unlock method call we will put in the finally ok
so that's the producer updated let's go ahead now and update the consumer and
hopefully you've got a grasp of what I've done their now so again just to
confirm by adding a try finally there's two advantages
firstly we've only got to put the unlock in one place in the finally clause but
secondly if we had another exception that somehow we weren't processing or
dealing with in the try block the finally clause code is going to be executed no
matter so that means we can keep pretty much guarantee that our unlock is
going to be called no matter what happens in the try block
ok so on to the consumer code and i put this in after the buffer lock try in their
and put our finally and we put our regular code up to but not including the
unlock put that in the try accept that import and then the buffer unlock we're
going to put in the finally code block and you can see that we only
need 1 unlock call now because unless an application crashes in a really bad way
we know that when a try block exits the code in the finally block will always
run and notice again I mentioned that but just to clarify that the call to
lock is outside the try catch block
we don't have to worry about the lock being released until we actually own it
so that's the reason we haven't got that in the try block and in fact if we
try to release a lock we don't own you will actually get an illegal monitors
state exception when we blocked we don't own the lock as such
so let's now run this and see if it works and we actually got
an error there and the error is because i haven't actually fixed the code
completely what I've done and I'm just going to close this run window down
now I mentioned that the idea of the try finally is to ensure that the buffer
lock.unlock is called each time and of course what i've done is i've also left
in the code in line 83 and on line 88
so consequently we're getting at an exception because we're calling an
unlock when we don't own the lock and this is because if we try to release the
lock that we don't own will get an illegal monitor state exception and
that's what you saw popping up on the screen so i need to remove this code
here and the code here as well because we know that no matter what
throughout his whole loop
anytime this code is process we're always going to unlock the code of
course in the try block doesn't get executed unless we've already got a lock
here on line 80 so in other words we guaranteed
we've got a lock when this code is executing and the finally code block
make sure that we only unlock once
so now that I've removed those and run it again we should find that it works as
we would expect and their you go the programs now working fine so let's have
a look at what else we can do with the locks i'm going to close down this window now
a thread can actually test to see whether a lock is available using the try lock
method
so if lock is available the thread will acquire the lock and continue executing if
it's not available
the thread won't block and alternatively can execute some
different code now for our particular application
there's no advantage to using try lock over lock
but let's change the code to use try lock anyway just to see what it actually
does
so I'm going to change the consumer code and specifically on line 80 where we've got
buffer lock.lock
I'm going to change that instead of saying bufferlock.lock I'm
going to put and if there so if...
....
if that's the case then and only then we want to execute the code and i'm going
to put all the try code in that block now
now so the scenario now is that only if that returns true the .trylock
which means that a lock is available then at that point because the lock was
available
we get the lock automatically and then we'll proceed to going through and
executing the code in the try block
so essentially try lock returns true then we'll execute the critical section of
code
if not we could do something else now we could put a print out to the console but
since our loop could execute hundreds of thousands of times we really don't want
to print anything to the console so an alternative thing to do would be to
count how many times try lock returns false and when we finally get the lock
and theirs data in the buffer will print the counter to the console then reset it
now we don't want to print the value of counter when the buffer is empty because
that could also happen thousands of times due to producer thread sleeping
after it adds each value
let's go ahead and change that so we need to introduce a variable....
.....and what we're going to do is down here after
this code is executed so in other words and we've done a test to see whether the
buffer is empty
if the buffer is empty and of course the loop exits and starts again
so if we get to this line 88 we know that we have got something in the
buffer at that point
let's print out the contents of the counter so we're going to print out....
.....and then we'll also reset it back to 0
then the other thing we can do outside of the block down here after the if
so in other words if you if try lock return false meaning we weren't able to
get a lock in there
we simply going to just increment the counter....so let's now run it and see
how high this count can go
look at the values of some of those numbers there they can actually go up
into the millions believe it or not
so you can see now why we wouldn't wanted to print anything to the console
we could have got potentially millions of lines of output and might have
crashed the program as well
what we can also do when we use try lock is we can set a time out period so if the
thread still waiting for the lock when the timeout expires it interrupted and
continues executing so to do that we just have to pass the time out period and the
time unit that were using to the method but i'll leave that as an
exercise for you to add it's not particularly hard to do
now we might want to see how many threads are waiting for a lock before we
try to acquire it
if they're our you know already a lot of threads waiting
we can run other code instead of blocking and wait for the lock
...
now I mentioned that one drawback to using synchronize blocks is that they
aren't first come first served
the lock interface doesn't dictate this behavior either but some implementations
do now the re-entrant lock constructor accepts a fairness parameter
when it's set to true the reentrant lock class will try to be fair
by giving them the lock 2 whichever thread has been waiting for it the
longest again if the thread comes along and finds out that a hundred threads are
already waiting and it knows the lock is a fair lock it might choose to terminate
instead of blocking on the lock when using a re-entrant lock
we can also check for the number of threads waiting for the lock
by calling the get queued length method now in our application the producer thread
spends most of its time sleeping and the critical sections of code for the
consumers are executed very quickly so there wouldn't be any threads waiting
for the lock most of the time and get zero but that is how we go about
checking using the get queued length method and just as an aside
you can check out more and find out more information about locks by having a look
again at the Oracle documentation and I'll paste the link on the browser
and it's also available as a resource
in this video make sure you check out the documentation it is very useful
there's also some other implementations
on the interface that are link to from the lock documentation as you
can see down here
all right we're going to finish this video now in the next video we are gonna start
taking a look at the executive service interface