Implementing Thread-Safe Singletons with Java AtomicReference and Java Volatile Variables.

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so the next topic we're gonna cover are various ways of implementing Singleton's that work correctly in multi-threaded programs and we're actually gonna look at two different variants one of the variants uses the double check locking pattern using Javas volatile variable technique and the other approach is going to be using an atomic reference and both of these topics are somewhat esoteric but they're fun and it helps to deepen your understanding of memory models and concurrency and various ways of getting atomicity in low-level parts of of java libraries and application code so let's go ahead and share our screen this particular example is in the e x28 folder here's the code so as I mentioned this basically is showing off how to implement the singleton pattern to work in a multi-threaded environment using a couple different techniques now first of all just to put a topic to rest yes I'm aware that the singleton pattern is that the go-to of patterns right it has a lot of a lot of haters out there who don't like singleton I'm I'm not a huge proponent of singleton there are times when it it's very useful the point here is not to debate whether Singleton's good or not it's to explain if you use it in a multi-threaded environment how can you make sure it works properly and a good place to learn about why this is a problem is to go out to the internet and search for double-checked lock game pattern and let's get rid of the Java singleton part if you just look up double check locking you will come to this Wikipedia link and there's a whole bunch of discussion about double check locking double check locking actually got its name and its start from a paper that that I wrote back in I think it was 1997 or so when I was a professor at Washington University and I discovered some weird problems were going on with the singleton pattern when used in multi-threaded environments and I wrote the double check locking optimization pattern as a description of how to get around these problems and if you take a look somewhere you can find my paper on it originally and this paper this topic also appears in the pattern or software architecture vol 2 book that I was the co-author for well as it turned out this pattern triggered a whole firestorm of controversy particularly in Java and particularly with respect to Java memory management semantics and memory model semantics and there's all kinds of discussions that go on and on and on about this the long and the short of it is you know skipping over about a decade worth of very polemic and passionate debate the Java community finally decided they would integrate support for double check locking into the Java Virtual Machine and so starting in Java 1.5 or JDK 5 or however you number it which I think was in 2004 so these had direct support for that so that's what we're gonna be talking about today we're gonna be showing how to use those mechanisms first let me just remind you how you use a singleton what you do is you want to make sure there's only one instance of an object and have a global entry point to get to the object but not use global variables so here you can see we use canonical singleton syntax we say singleton instance and then we can call fields or set fields get fields on that one and only instance so we can set the field to say 100 we can get the field printed out we can go ahead and have a different singleton with a different implementation set the field get the field and the problem is that singles them out of the box is not thread safe so you can have all kinds of weird problems so these examples demonstrate how to do thread safe singleton and you can see it this example is very very simple but it just shows that you get these results okay so let's go take a look at the two different ways to do this the two different ways are either by using volatile variables in Java in Java 5 or later in conjunction with a double check locking optimization pattern or an atomic reference and there are other examples of ways to do it as well if you go to the double check locking Wikipedia link you'll find lots of other explanations how to do this in Java and other languages so here's the way to do it with a bottle two variable and the double check locking optimization pattern in Java 1.5 and beyond so you go ahead and make yourself a static field which is the one and only instance of the singleton and we make that volatile very important have volatile there and then we can define a bunch of other non-static fields that'll be accessed through the singleton object and then here's the method here's the entry point into our singleton Asst called instance and what we do is we come along here and we make a copy of the volatile singleton so we copy the reference so we now have a local variable called inst which is a copy to the singleton which is up here and that value is either gonna be null or non null and because it's volatile it's atomically null or non null so then we go ahead and we check to see whether inst is equal to null and notice there's no locking here and so we're relying on the fact that reading a volatile is an atomic operation so the first time in then in swill be null in which case we're then gonna have to go ahead and synchronize on some mutex in this case that singleton the dot class object is every Java object has a class object associated with it there's one of them so we're gonna synchronize on that then we're gonna go ahead and reread the singleton again with another vowel to read and at this point we perform the second check so this is the double check in the double check locking and if inst is still null it means we're the first time we've gotten through here and then we go ahead and make ourselves the one and only singleton we assign it to inst and then we assign it to s singleton that's an atomic right and then we fall out of the if statement and we return inst so either inst was already non-null atomically when we came in here in which case were just returning a previous initialized value or the first time in it was null and then we went ahead and synchronized so only one thread if there were multiple threads we'll be doing this initialization of the singleton there's only one at a time is gonna be doing that so that's why it's the double check locking optimization pattern checks it twice the first time in so that's how you do it with volatile and I like this approach it's very clean it generalizes to other kinds of things I find it easy to understand there's other ways of doing it in Java they don't require using volatile but this is one cool way to do it now let's go ahead and take a look at another way to do it this is in a way that uses a so called atomic reference an atomic reference is one of those Atomics classes like atomic long atomic boolean atomic integer and so on and accept an atomic reference can work on arbitrary objects not just in-store Long's so what we're gonna do here is we're gonna define ourselves a static field called s singleton AR for atomic reference and we're gonna give it an initialization of a null and I guess I can get rid of that cuz I don't need it um so it's gonna start out being atomically initialized to null and an atomic references we'll see has some certain cool properties that we can do for essentially compare and swap like operations we're then gonna define some non static field so I just have one here but I can have many we have setters and getters for them so here's the instance method for the singleton AR implementation of this stuff so this is the variant for atomic references into instance the first thing we do without any other locks being held is we get the current value of the singleton which could start out being null so that may be a null read if it isn't all read then we know that we haven't been initialized yet so we come in here and we go ahead and make ourselves an instance of singleton AR now here's where things diverge a bit from the double check locking version of this pattern this version of the pattern will in fact atomically assign an instance of single to they are properly but we can have multiple calls to the constructor because multiple threads could all come through simultaneously and find that singleton was null so this particular approach only works if you're comfortable having your constructors called more than once if constructors are no ops doesn't matter if your constructors do something more interesting then of course you have to be careful especially they have side effects so we get back a singleton here which is one of these guys and then we come down and we call the compare and set method which is basically an atomic compare and swap operation that's how it's implemented under the hood using low-level Java memory management primitives like compare and swap and what that does is it says if the current value of s singleton AR is null then go ahead and set the newly allocated singleton atomically so we're guaranteed that that will be done once and it'll be done correctly and if any other thread comes through only the first one will succeed and you know you succeed if you get a true back which meant it worked if you get false back that meant you weren't the first one in in that case we know that the singleton has been initialized so we go ahead and simply do a get call to get the latest value or the current value of the singleton in the event when we're all done we return the singletons current value so once again really cool little technique the downside of course is you can only have you have to be aware of the fact that your constructor could be called multiple times in contrast the version that's over here the one for singleton V that does not have that property so this will guarantee that the constructor is only called one time it's a little bit more complicated because we have to use the volatile and the double check and so on but the nice part is that the semantics are more coherent because you don't have to worry about constructors that will do things incorrectly if they have side-effects okay so that was just a simple fun little example kind of talking about some rather esoteric aspects of Java synchronization and how they interact interact with the memory model that you've got with Java so let's go ahead and see if there's any questions about this topic so Java guarantees a singleton initialization of a static field yeah that's that's what's called the lazy holder initialization approach and that's actually described also in the double check locking optimization page it's another variant of how to get and initialize singleton but or an initialized variable that's only got one value so let's see so um the main about the only reason I can think of ever wanting to do something like this is that this version of singleton is a bit more dynamic so the other version does it on load time I've thought long and hard about that in and haven't really found a lot of use cases where singleton is more effective than lazy holder I guess probably the main thing would be if you're porting code from another language that's not Java and you use Singleton's it's a more straightforward way to port your code because your other code of course wasn't going to be taking advantage of Java's lazy holder model and so you'd have to do some additional rework but um just to be complete let's go ahead over here so I mentioned this and let's go look at double check locking optimization you can see this and I think somewhere in here they show the lazy holder thing yeah here we go it's it's in here so you can take a look and think this is the lazy holder model there's a bunch of different ways to do that and then there's also this newer concept called VAR handles which is yet another interesting mechanism that is an attempt at trying to get away from having to use the low-level compare-and-swap operations that you would find in java unsafe class and this is yet another way of being able to do these kinds of things using bar handles so there's many different ways to skin a cat as people like to say um Oh another question why do we use copy intz what's the problem if we don't use the copy so let's see I'll go back and take a look at the code that goes along with that so you can see what question refers to share the here we go so it's this part here um that's just the recommended way to get this thing to work so to to find out the the answer to why it works that way you'd probably have to send an email to Doug Lee but they decided that was the pattern that they wanted to follow I'm not sure if it has to do with some optimizations or trying to teach the JVM to be smart about optimizing things but did you have to do this so you're doing an atomic read here now you're checking with the atomic read there and then here we do another atomic read so it's probably to force an atomic read of this static variable that otherwise might be cached and so therefore we want to make sure that if we we get it read properly that that's my guess but again Doug Lee would know he's the he's the expert on all that
Info
Channel: Douglas Schmidt
Views: 2,593
Rating: undefined out of 5
Keywords:
Id: L1Pq9-bx7KY
Channel Id: undefined
Length: 14min 8sec (848 seconds)
Published: Sat May 16 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.