A Tale of a Careless Store Owner

Totally not an anti-pattern of well-written micro-services.

Once upon a time in a faraway kingdom, there was a tiny convenience store. It was small but very popular among the locals especially because of the apples. This store had to offer the finest, the biggest and the sweetest apples in town.

Customers came in long queues, and the store owner filled up their baskets with fresh apples all day long. Life couldn’t be any happier. Except that the store owner liked to sleep very much. He didn’t like it when a trader was knocking on his door early in the morning.

“Get-up sir, I brought the apple” – the trader said.
“Why can’t you come later when all my other suppliers do?” – the store owner replied.
“I have to travel to the farthest corners of the kingdom, I need to be on my way as soon as I can” – the merchant persisted.
“Eh, I give you the key to the store. Take it to the blacksmith he will make a copy for you” – closed the debate the owner.

For the time being, everybody was happy. The store owner could sleep longer, and the trader could unload his carriages any time he wished.
“What could possibly go wrong?!”
“Everybody here has the best of intentions.”
“What a fine solution!” – the store owner gave himself a pat on the back.

Months passed by, and life was thrilling. The kingdom was expanding, faraway lands have been conquered by the king. It was no longer just apples on the shelves. The early morning trader on his way back brought oranges, bananas, papayas, and much more. While the owner was asleep, he opened the storehouse again and unloaded the fruits all over the place. He mixed apples with oranges, grapes with bananas and so on.

“He really does create a mess” – the owner muttered.
“Ah, nevermind” – he continued.
“My dear customers should pick what they need or better cook a mixed fruit pie.” – concluded the debate with himself.

Life wasn’t as good as before, but still better than at most places. There was a small problem though. There was a cook who wanted the apples earlier than the shop was open.
“I have plenty of things to do, I cannot wait until that sleepyhead wakes up” – he argued.
“I don’t care how you do it, but give me my apples at dawn” – he was yelling at his scullion.
His servant was a smart boy. He knew that while the early morning trader empties his baskets the store will be unlocked. He went in and collected the finest of the apples. The cook was very pleased even gave his scullion a pay rise.

Months flew by, spring came. The apple trees were in full bloom. This was when a real tragedy occurred. An evil wizard sent a blizzard on the kingdom and froze half of the apple trees. There were famine and poverty. The king gave a strict rule to the shop owner:
“No one should buy more than two apples a day” – he ordered.
“Yes, my lord” – the store owner replied.

The shop owner did the best he could, but somehow he was always short on apples. Some customers left empty-handed, they could not even buy their two apples a day. Needless to say, the impatient chef never had to stand in line. There was always a sweet-smelling pie on his table.

The store owner has been punished harshly by the king for his negligence.
“What should I do?” – asked the owner desperately.
“I totally lost control of my business” – realized the situation.
“It would be painful to fix it, though” – he compromised.

So, the store owner continued in his old ways. As a result, some of his clients were happy some of the time. And this is how they lived sort-of happily ever after.

This is a fictional story. Any resemblance to computer systems where multiple applications or services without proper boundaries share and modify common data is purely coincidental.

Book Review: Effective Java by Joshua Bloch

This is not your ordinary book review. You have been warned!

So, you’re working with Java, right?
Go, read this book!

You’ve learned Java at the university, or in a boot camp, perhaps as a self-thought programmer?
Go, read this book!

Maybe you’re thinking about becoming a Java developer, hm?
Well, learn the basics, and then read this book!

I think you get the point.

My shortest book review ever


Cover - Effective Java Book Review

It’s a very pragmatic book in nature. It contains 90 items divided into 12 sections each centered around different topics like lambdas, exceptions, serialization, etc. Writing Java code which works is one thing. This book will help you to write code which is effective, clear and easy to maintain.

Every Java developer should read this book. Period.

Take Action

Knowledge without action is worthless, especially when we take this book. After every item in this book I stopped reading and started thinking: is this something I can apply to the project I’m working on?

Then a searched. I found some issues. And I fixed them.

More often than not the code I had to patch was mine. But that’s OK. That is called progress. None of us writes perfect code, but this book helps us to get better at it. If you read this book as a junior developer, it might prevent you from making a lot of mistakes in the first place!

As the Java language evolves, so does this book. The current one is the 3rd edition. I’ll make sure to read the next one when it comes out to see if there are any new best practices. If there will be only one Java book you read this year, let it be this one. And of course, don’t forget to take action on it!

Before you go

In case you’ve already read this book, feel free to share your experience in the comments below. The most eye-opening items for me were: static factory methods, enum sets, and the cost of reflection. Which ones were yours?

Buy on Amazon: Effective Java, 3rd Edition, Joshua Bloch
Read another one of my book reviews: Console Wars, Blake J. Harris.

Fractals in Software Development

Fractals are everywhere, from nature to software development – This thought struck me when I was listening to one of those corporate telephone meetings with 15+ people on the line where none of the participants really pays attention to. Except for the speaker, I guess.

You asking what fractals are? Fractals are abstract mathematical objects that commonly exhibit similar patterns at increasingly small scales. There are artificially created fractals like the Mandelbrot set or Menger sponge (see below), but natural fractals can be found in snowflakes, clouds, and tree branches too.

Menger Sponge
Menger sponge also known as Sierpinski cube

What the hell all of that has to do with software development? Bare with me, I will tell you in a minute.

The enlightenment came to me when I was listening to my bosses boss describing future plans and how the firm is keen on “finding synergies”. Finding synergies basically means trying not to reinvent the wheel every time some new functionality is required, but instead use something that already exists.

When we programmers write a function or a method we do it because there are some steps which we would like to execute together over and over again. Common functionalities are then coupled into classes and libraries for better re-use. When writing an application we try to break it down into multiple components where ideally none of the functionality is duplicated. Building a platform consisting of multiple applications or services is no different. One of the purposes of using a microservice architecture is to segregate well-defined functionalities into services which can be utilized by other services. When you have multiple independent systems under your firm’s umbrella it makes sense to investigate how could existing solutions deployed to new departments to address similar needs. In other words: finding synergies. In my eyes, all these use cases represent the same thing on a different fractal dimension.

I was sitting at my desk and I felt like I was Buddha under the fig tree. This is when I realized that under the surface programmers have more in common with high-level management than a lot of us initially might think. And that the end of the day software development obeys the same laws of nature as trees, clouds, or snowflakes.

Fractal TreesFractal CloudsFractal Snowflake

What’s Missing from Mastering Spring 5.0

Mastering Spring 5.0 book cover
Mastering Spring 5.0 book cover from Amazon.com

Chapter Dependency Injection

Now that Java 9 and Spring Framework 5.0 have been released and end-of-year is just around the corner I thought I’ll keep my knowledge up-to-date and read Mastering Spring 5.0 from Packt Publishing. Based on the first two chapters it seems to be giving a good overview of the framework in an easy to follow fashion. However, after reading the 2nd chapter I could not resist writing a post about what I believe is missing from it. Despite the book targeting experienced developers when it discusses IoC – Inversion of Control, it fails to mention two advanced but very important features I’ll now share with you.

Defining beans within configuration classes

When the book compares XML configuration with annotations it states that “Classes using annotations are no longer simple POJOs because they are using framework-specific annotations.” Example:

@Service
public class UserService {
  @Autowired
  private UserRepository repository;
...
}

This is why my preferred way of wiring components together is by configuration classes.

@Configuration
public ServiceConfiguration {
  @Autowired
  private UserRepository repository;
  @Bean
  public UserService userService() {
    return new UserVervice(repository);
  }
...
}

By doing so the @Service tag becomes unnecessary, plus I can keep all Spring specific annotations in one place: in the configuration classes. This is how I keep my repositories and services as clean POJOs.

The other topic I was missing from the 2nd chapter was how XML and annotation based configurations can work hand in hand.

Mixing XML and Java based configuration

When working on a “legacy” project which is still using XML based context configuration you can find yourself in a situation where you need to add some new configuration. And because it’s not 2009 you would like to use annotations. What can you do? Rewriting the whole configuration might require a significant amount of time and can be error-prone. Even if you aspire to do so in the long term, the best you can do is to start with incremental improvements. The Spring Framework provides a way to import Java configuration files into the XML context configuration.

First, you write your Java configuration like this:

@Configuration
class NewConfig {
  @Bean
  public DataRepostory dataRepository() {
  ....
  }
}

… and then import it into your XML config:

<bean class="org.randomdeveloper.spring5.config.NewConfig" />

By doing so you will have the ability to reference your new beans in the XML configuration – if required.

I hope you find my tips useful. Despite these shortcomings, I still think that Mastering Spring 5.0 is a very well written book. I suspect chapters about Spring Cloud will contain a lot of stuff that is new for me 😉

Google Doodle Coding Bunny Level 6 Solution

Have you seen the google doodle celebrating 50 years of kids coding? You can find it here in case you’ve missed it:



This is a mini-game where you can program the movement of the bunny in order to eat all the carrots on the field. The game consists of six difficulty levels. To find the “shortest” solution to the last level can be challenging even for seasoned professionals. How can it be? Possibly because experienced developers try to find the optimal solution where the bunny collects the carrots in the least amount of steps. Whereas, the authors are looking for the shortest code – least amount of symbols to describe the algorithm.

Spoiler alert! Please find my solution below. It takes advantage of the fact that our bunny cannot fall into the abyss – as if there was an invisible fence around the field.

Frequent Coding Interview Exercises

I often conduct Java core competency interviews to support my current firm’s effort to bring in new talent. Most candidates are coming in reasonably prepared regarding theoretical knowledge at least. However, they often surprise me how a coding exercise catches them off guard – typical for fresh graduates. Here the intention is to use the produced code as a starting point for further discussions around coding paradigms and language constructs. This often gets crippled because we hardly finish with the coding bit in time.

You might think that the exercises are way too complex, but the truth is that they are not. I invite you to have a look if you are interested. In order to help rookie interviewers and interviewees, I decided to create a git repository to collect the most common coding assignments. The repository might be small for now, but I intend to add more stuff to it over time. Because I truly believe in test-driven-development each assignment has its own dedicated test class. To be up-to-date with the latest technology tests are written using the new JUnit5 framework.

The repository is available here: https://github.com/kurtiszabi/interview-coding-exercises

Console Wars by Blake J. Harris

There was a time when Sony and Microsoft didn’t know the first thing about gaming. A time when the term home video game console was equivalent to the Nintendo Entertainment System (NES). This was the time when Sega, one of the rivals, decided to break the status quo. If they had managed to keep their momentum perhaps the videogame industry would look very different to what it is today.

Console Wars: Sega, Nintendo and the Battle that Defined a Generation
Console Wars: Sega, Nintendo and the Battle that Defined a Generation book cover

The book from Blake J. Harris, Console Wars: Sega, Nintendo and the Battle that Defined a Generation, is based not just on publicly available materials like conference presentations, newspaper articles but also on more than 200 hundred personal interviews with key figures from the industry at the time. Such a key figure was Tom Kalinske who was recruited to be president and CEO of Sega of America in 1990. The book centers around his figure and his team’s efforts to overthrow Nintendo.

To be in the gaming industry in the early 90’s must have been a crazy rollercoaster ride. The market was growing at an ever faster speed and the user base exploded astronomically. For Sega, the key question was how to combine technology with clever marketing to fight from the underdog position. To better understand the differences just have a look at their mascots. Nintendo’s best-known character Luigi is a nice, jovial guy who is jumping around the Mushroom Kingdom to save Princess Peach. In contrast, Sonic the Hedgehog rushes through levels at a breathtaking speed and with an edgy attitude.

Sonic the Hedgehog
Sonic the Hedgehog

If it was only about the characters and the gaming experience these two companies had to offer the story wouldn’t be so interesting. But if you add into the mix the cultural differences between the Amercian and Japan parties on both sides. The problems of distribution, fighting for the shelves space at major retailers. Deciding which R&D project to support and which one to kill. Answering the question whether having the superior technology is enough to win the battle? As you can see there’s a lot of room for clever thinking – and a lot of chance for things to go south. How about pricing? Does printing brand new catalogs overnight just to announce a price drop a few hours earlier than Nintendo to steal their thunder sounds crazy? At Sega, this level of dedication was rather a rule than an exception.

The author of the book did a remarkable job of describing an era by telling the stories of the individuals who lived through it. Their happy, sad and sometimes bitter sweet moments, their professional victories and individual losses make the book very personal. What I really liked about their story is that no matter how many challenges they had to face still they managed to have fun in growing this niche market into a multi billion dollar industry while also shaping an entire generation.

Sega Genesis
Genesis a.k.a. Mega Drive: 16-bit home video game console from Sega

Nevertheless, Sega has faded away and today Nintendo is only the 3rd by market share on the console market. How did it happen? Well, it has a lot to do with the PlayStation and Steve Race’s “299$” speech. But did you know that Sony has originally planned to release a console with Sega? For more on that and for other interesting anecdotes I suggest to get hold of the book. Have I mentioned it’s more than 500 pages? Long but worth every page 🙂

Weak Reference. What?

Q: What are weak references good for?
A: They have something to do with garbage collection.

While the answer above is certainly better than not knowing about weak references at all; any aspiring senior Java developer should know a bit more about them. Especially, when we consider that they are around since Java version 1.2. So, let’s dive a little deeper into this topic.

Strong references are ordinary Java references we use every day. For example:

Widget widget = new Widget();

Objects survive garbage collection and stay in the memory heap as long as they are reachable through strong references.

Let’s just stop here for a minute. Those who are only interested in weak references can skip this section or come back to it later. The rest of you should try to answer the question: Where does the garbage collector start working from? There are special objects called Garbage-Collection roots that are always reachable and serve as starting points for GC operations. There are the following four different kinds of such GC roots in Java:

  • Local variables kept alive by the stack of a thread.
  • Active Java threads are always considered live objects.
  • Static references are kept alive as long as the Class itself is not garbage collected.
  • JNI references are Java objects that the native code has created as a part of a Java Native Interface call. Such objects represent a very special form of GC root and are treated distinctively because the JVM has no idea if they are still referenced by the native code or not.

Weak references are not strong enough to keep the objects from being garbage-collected. You can create a weak reference like this:

WeakReference weakWidget = new WeakReference(widget);

In order to get back the actual widget, you call weakWidget.get(). Because the widget can get garbage-collected at any time, such a call can suddenly start returning null. There is a related class WeakHashMap where the keys (not the values!) are weak references and when the key gets garbage-collected the value is removed from the map automatically. This behavior makes it an ideal candidate for implementing cache functionality.

Would you like to know when a weakly referenced object has been garbage collected? This is what ReferenceQueue is for. If you pass it into the constructor of a weak reference it will be automatically put on the queue once the referenced object becomes a garbage.

There are different degrees of weakness. Soft references are between strong and weak references. They are less eager to throw away their object. In practice, they retain it as long as there is plenty of memory – but this is not a guarantee.

Phantom references are the weakest form of a reference: their get() method always returns null. Phantom references are only enqueued when the object has been already finalized. Which leads us to the only scenario I can think of in which phantom references can be useful: to determine when exactly a given object has been garbage-collected. You can create a phantom reference calling it’s only constructor:

PhantomReference(T referent, ReferenceQueue< ? Super T> q)

Did you notice something? Unlike soft and weak references, phantom references are not automatically cleared by the garbage collector as they are enqueued. An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable [5].

I Hope you found this post useful 😉

Resources:
[1] https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references
[2] https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works
[3] https://docs.oracle.com/javase/7/docs/api/java/lang/ref/WeakReference.html
[4] https://docs.oracle.com/javase/7/docs/api/java/util/WeakHashMap.html
[5] https://docs.oracle.com/javase/7/docs/api/java/lang/ref/PhantomReference.html

Composing Objects – Java Concurrency in Practice

Hi, Java dudes! In my previous post, Sharing Objects – Java Concurrency in Practice, I reviewed how objects can be published and accessed from multiple threads in a safe manner. Today’s topic is how thread-safe components can be combined and enriched with new functionality.

While it is possible to write a thread-safe application that stores all its state in public static fields, but it is much easier if we do it by combining thread-safe components. By doing so we can delegate every thread-safety issue to the right application tier where it needs to be handled. Let the thread-safe class deal with how access to its inner state has to be synchronized. While on a higher level, we can think about how these thread-safe components can be used to represent the application state properly.

In order to prevent concurrency issues, we have to think about state ownership. This is something that is not part of the language itself but it is defined by class design. Therefore programmers have a great autonomy to make the right choices – and the bad ones too. A class usually does not own the objects passed to its methods. In many cases, ownership and encapsulation go together – the object encapsulates the state it owns and owns the state it encapsulates. Collections often share ownership of the contained objects with the client code that inserted them into the collection.

The Java monitor pattern is an example for ownership by encapsulation.

An object following the Java monitor pattern encapsulates all its mutable state and guards it with the object’s own intrinsic lock [JCiP]

Synchronized collections offered by the standard JDK follow the Java monitor pattern. Synchronization is implemented as a wrapper layer that controls all access to the underlying collection. A synchronized list of Strings can be created like this:

import java.util.Collections;
...
List syncStrings = Collections.synchronizedList(new ArrayList());

It is critical that the reference to the backing collection must not escape otherwise it can undermine thread-safety. Operations over a synchronized collection are mutually exclusive – only one thread at a time can work with the collection. In most cases, this is too restrictive and result in poor application performance. There are other alternatives but more on that later.

Now let’s suppose we have a class that is already thread-safe but misses an operation that we need. What can we do in such a situation? We have multiple options, but not all of them are correct:

  • put new synchronized code in a helper class, ☠
  • extend the original class, ☠
  • modify the original class to support the desired operation, or
  • add new functionality in a class encapsulating the original one.

Using a synchronized method of a helper class is probably the worst possible approach. Whatever lock the parameter object uses it definitely won’t be the intrinsic lock of the helper method. It only gives us the illusion of safety while other threads can still modify the state of the passed in object.

Extending the original class – if possible – could work, but it’s fragile because the underlying class might silently change its synchronization policy and jeopardize thread-safety of the extending class. Another problem is that not all classes expose enough of their state to make this approach possible (private lock).

The safest way to add a new atomic operation is to modify the original class consistently with its original design. In this case, all code that implements the synchronization policy is contained in a single source file – easier to understand and maintain.

If changing the existing class is not an option then using composition is the best alternative. This is how standard synchronized collections are implemented. And just as with synchronized collections, it’s crucial that all access to the underlying object must go through the wrapper class.

Don’t force clients or other developers to make risky guesses about the thread-safety of your code.

Document a class’s thread safety guarantees for its clients; document its synchronization policy for its maintainers. [JCiP]

Did you know that java.text.SimpleDateFormat is not thread-safe? Okay, maybe you did, but did you know that it wasn’t explicitly documented until JDK 1.4? And this is the worst that can happen: you assume that something is thread-safe that is not. This is why you should always document your code.

...
@ThreadSafe
public class ImprovedList<T> {

    @GuardedBy("this")
    private final List<T> innerList;

    public ImprovedList(List<T> list) {
        this.innerList = list;
    }

    public synchronized void putIfAbsent(T elem) {
        if (!innerList.contains(elem)) {
            innerList.add(elem);
        }
    }
}

Annotations @ThreadSafe and @GuardedBy are not part of the standard JDK.

That was all for now. Next week we are going to look at what building blocks Java provides that we can use as Lego™ pieces to build our application. Best wishes until we meet again here 😉

P.S.: instead of java.text.SimpleDateFormat use its thread-safe Java 8 alternative the java.time.format.DateTimeFormatter – where applicable.

Resource
[JCiP] Java Concurrency in Practice by Brian Goetz, ISBN-10: 0321349601

Sharing Objects – Java Concurrency in Practice

Welcome, Java enthusiast! Today I’m going to deal with issues around sharing objects in a multi-threaded environment. In last week’s post, Java Concurrency in Practice – Thread Safety, I was focusing on preventing multiple threads from accessing a shared state at the same time. In this post, I’m going to deal with a more subtle aspect of synchronization: visibility. How to publish changes by one thread so they can be safely read by other threads.

public class NotVisible {

  private static class WorkerThread extends Thread {
    public int value = 1;
    public boolean finished = false;
    public void run() {
      while (!finished)
        Thread.yield();
      System.out.println(value);
    }
  }
  
  public static void main(String[] args) {
    WorkerThread t = new WorkerThread();
    t.start();
    t.value = 2;
    t.finished = true;
  }

}

The class NotVisible demonstrates what can go wrong without proper synchronization. While it seems reasonable to assume that the code above will print 2. Actually, it can loop forever because there is no guarantee that the values set by the main thread will be visible in the “worker” thread. Nor, that they will become visible in the same order. Therefore it can happen that it will finish, but print 1 instead of 2.

“in the absence of synchronization, the Java Memory Model permits the compiler to reorder operations and cache values in registers, and permits CPUs to reorder operations and cache values in processor-specific caches.” [JCiP]

Therefore:

“Attempts to reason about the order in which memory actions ‘must’ happen in insufficiently synchronized multithreaded programs will almost certainly be incorrect.” [JCiP]

In insufficiently synchronized programs reader threads can see out-of-date values. Stale data can cause serious issues and failures like unexpected exceptions, broken computations, corrupted data, or infinite loops.

Intrinsic locking is one of the mechanisms that can guarantee that changes made by one thread will be visible to other threads in a predictable manner.

“Locking is not just about mutual exclusion; it is also about memory visibility. To ensure that all threads see the most up-to-date values of shared mutable variables, the reading and writing threads must synchronize on a common lock.” [JCiP]

Meaning that:

“When thread A executes a synchronized block, and subsequently thread B enters a synchronized block guarded by the same lock, the values of variables that were visible to A prior to releasing the lock are guaranteed to be visible to B upon acquiring the lock.” [JCiP]

There is an alternative way to propagate changes predictably: using volatile variables. When a field is declared volatile the compiler and the JVM is put on notice that this variable is shared and should not be cached nor should be access to it re-ordered with other memory operations. Volatile variables provide visibility but not atomicity. Therefore we cannot use them when a write to the variable depends on its current value.

“Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.” [JCiP]

Publishing an object means to make it available outside of its current scope. Using good OOP practices like encapsulation is not necessary for writing safe concurrent programs. However, it certainly makes easier to reason about correctness. Publishing internal state variables can compromise not just encapsulation but the thread-safety of your application. Other classes or threads can intentionally or carelessly misuse the published state and break your design. Sometimes, publishing is obvious e.g. storing a reference in a public static field. In other cases, it can be more subtle like passing an object to an overrideable – neither private or final – method.

An object is in a predictable, consistent state only after its constructor returns. This is why:

“Do not allow the this reference to escape during construction.” [JCiP]

We can let the this reference escape during construction either explicitly (by passing it) or implicitly by instantiating an inner class of the owning object.

No synchronization is needed if the data is accessed only from a single thread. What an ingenious idea! Actually, it is so important it has its own name: thread-confinement. For example JDBC connection pools use thread confinement to ensure correct program behaviour. The Connection object itself is not thread-safe but the pool won’t dispense the same connection to another thread until it is returned from the owning thread – which acquired the connection in a thread-safe manner from the pool.

ThreadLocal class allows us to maintain a separate copy of a value on a per-thread basis. It provides getter and setter methods to return or set the value for the currently executing thread respectively. When a thread calls get for the first time the initialValue() method is executed to construct the new value.

Stack confinement is a special form of thread confinement. Because local variables only live on the executing thread’s stack. If the data is only reachable through local variables thread-safety is not an issue.

An immutable object is one whose state is cannot be changed after construction.

“Immutable objects are always thread-safe.” [JCiP]

It’s technically possible to have an immutable object without all fields being final. The String class lazily computes the hash code the first time it is actually needed but because this is derived deterministically from an immutable state String is still considered immutable.

So far, we have been focusing how not to share objects among multiple threads. Of course, sometimes we simply have to and then we have t do it safely. If the object we would like to share is not immutable we have to follow the safe publication idioms listed below.

To publish an object safely, both the reference to the object and the object’s state must be made visible to other threads at the same time. A properly constructed object can be safely published by:

  • Initializing an object reference from a static initializer
  • Storing a reference to it into a volatile field or AtomicReference
  • Storing a reference to it into a final field of a properly constructed object
  • Storing a reference to it into a field that is properly guarded by a lock
[JCiP]

The Java Memory Model model offers a special guarantee for immutable objects: they don’t have to be published safely. But, they have to be constructed properly: all fields have to be final and their state must not change. This guarantee does not hold for effectively immutable objects. They have to be published safely but can be later accessed without any further synchronization.

Whoa, this was a lot of information. I’m so glad that I decided to write my own study notes because it helps me a lot to better understand what I’ve read. I also hope that I can help others as well. I recommend to read the book and use my notes to recapitulate 😉

Resource
[JCiP] Java Concurrency in Practice by Brian Goetz, ISBN-10: 0321349601