
Java 8: New features in ConcurrentHashMap - mariushe
http://www.deadcoderising.com/2016-01-17-java-8-new-features-in-concurrenthashmap/
======
w8rbt
Java has really come a long way. I'm not sure why it's not hyped more. I guess
it's like C++ (another widely used old man's language that runs the world).

~~~
legulere
\- Oracle has hands on it and does questionable things like suing google

\- Everything Java does, C# does as good or better. In general for everything
in Java you could easily find a better way to do it.

\- Java has lots of warts, many to have the language be backwards-compatible.
(switch-case, enums, UTF-16 encoding, generics, null, difference between
objects and basic types,...)

\- Java is tied to old technologies. There are no standard JSON libraries,
however even XSLT is included in java se.

\- You need to use Java with an IDE. Eclipse is horrible to use (even
scrolling lags here) and IntelliJ costs money.

Still java is a mature and solid language. There is just nothing to hype about
it.

~~~
sdfsdufihwfuhdf
You must not have used eclipse for a while :) . I use Visual Studio at work
and Eclipse at home and they're comparable these days. I agree that years ago
Eclipse was awful.

\- Every language has switch-case and enums? I don't get what you're trying to
say there.

\- Generics are fine?

\- Every language supports null on objects?

\- Nearly every language treats null and primitive types differently for
performance reasons

\- Java has three popular JSON parsers and they're all faster than .NET's
builtin serialization. To get similar performance you need to use Newtonsoft
which isn't builtin to C# either :).

\- You don't need an IDE, it's just stupid to develop without one because
they're so helpful. Nothing is stopping you from running javac on the command
line.

You can't do a LOT of things java can do in C# because third party library
support pales in comparison. A lot of databases and open source software don't
have c# clients. If you're dealing with big data or ML you'll find almost
nothing in C# land.

I feel like you haven't used Java in a long time. Things are much better than
Java 6 days

~~~
legulere
Maybe I should expand a bit on my points, as the problems do not seem so
apparent.

\- Java has the same horrible switch case with the error prone break as C. If
you want exhaustive matching on enums you will end up with a useless default
case. If you ever worked with a programming language with pattern matching you
will feel the pain.

\- This might be a bit opinionated but I think enums are not enough. Sum
types/tagged unions/variant types/disjoint unions/whatever you like to call
them are pretty useful.

\- Generics in Java are highly limited. Part of that is because generics were
added as an afterthought and are implemented using type erasure. Both
functional programming languages like Haskell or imperative programming
languages like C++ or Rust offer more powerful generics that can sometimes
help abstract things more elegant.

\- The problem is that all objects are nullable by default and you cannot
specify that e.g. parameters or results are never null. This leads to
boilerplate null checking and missing handling of null cases. Everybody that
touched java probably saw quite some amounts of NullPointerExceptions. Kotlin
for instance offers types that by default cannot be null, with TypeScript this
exists if you turn on an option of the compiler.

\- You can offer pretty much everything you offer for objects also for
primitive types. In fact, this is what Project Valhalla tries with value types
and specialisation.

\- My comment on IDEs was more about the need to use an IDE being bigger with
Java than for instance C, while eclipse is often annoying. I am using eclipse
daily currently because I work on some Java code for my Master's thesis.

~~~
nimchimpsky
"IDE being bigger with Java than for instance C "

Really, why ?

I can't imagine working without an ide for any language. Back in the dark days
I did html/javascript in notepad.

------
astral303
To me, the single most useful/important addition to Java 8 ConcurrentHashMap
is the addition and implementation of "computeIfAbsent".

With CHM and computeIfAbsent, it's easy to write lazy loading "by key" (i.e.
simple caches). Prior to Java 8, I had to resort to Guava's LoadingCache.

~~~
alkonaut
Wait, it had a concurrenthashmap but no way to "get or add"? Is a concurrent
hash map even useful without that? (I suppose you could always write your own
with some read/write locking but...wow)

~~~
LgWoodenBadger
ConcurrentMap has always had putIfAbsent(). That requires the thing that
you're putting to have been constructed prior to the call.

computeIfAbsent() allows you to pass in a lambda (a Function really) which
will only be called if necessary, thus avoiding a potentially unnecessary
Object creation/etc.

------
jaimex2
After Java 8 is there any benefit to using Scala? It seems Java has caught up
and has the benefit of being lighter.

~~~
joneholland
The Java streams syntax is still painful.

~~~
kedean
I got used to the syntax pretty fast, to the point that I actually kind of
like it. My complaint about streams is how slow they can be. If you're doing
collection transformations often, the time spent turning your objects into
streams can kill your performance. I'm worried it's going to reinforce the old
myth that functional programming is implicitly slow.

~~~
spullara
It will be a lot better with value types in Java 10 (hopefully).

------
meddlepal
This is old and they didn't even touch on the most useful thing in Java 8
ConcurrentHashMap which is computeIfAbsent.

~~~
pvg
Like most of these, that's part of Map in general.

~~~
astral303
The _thread-safe_ implementation of computeIfAbsent in ConcurrentHashMap is
the real improvement. "computeIfAbsent" on a non-concurrent Map is merely
syntactic sugar.

~~~
pvg
Neither is 'merely syntactic sugar', take a look at their respective
implementations. It's hardly surprising that a method on a class named
'ConcurrentHashMap' in 'java.util.concurrent' provides certain concurrency-
related guarantees. It's the point of the whole thing and is written on the
tin.

~~~
astral303
Default Java 8 Map implementation is merely: "get X. if X absent, compute for
X, put X." These lines of code have been written over and over again for
anyone using a Map in Java, no doubt. It is entirely trivial and almost
impossible to get wrong writing it out on your own.

Whereas writing a _performant_ concurrent "computeIfAbsent" is extremely non-
trivial and if you try to do it yourself, you have a high chance of getting it
wrong or slow.

Therefore, CHM "computeIfAbsent" is new and exciting, and Map
"computeIfAbsent" is "could care less".

~~~
pvg
_Default Java 8 Map implementation is merely_

Worth noting that's not actually the HashMap implementation.

------
jat850
I've spent the past 4-5 days going over and over this page:

[http://winterbe.com/posts/2015/04/07/java8-concurrency-
tutor...](http://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-
thread-executor-examples/)

Talks about some other useful topics as well. It's probably not news to
everyone, but the third part covers a fair bit of CM/CHM.

------
hyperpape
What is it about Java that gives us so many variants of each method instead of
more composable primitives?

I'm thinking of:

    
    
        forEach(long parallelismThreshold, BiConsumer<? super K,? super V> action)
        forEach(long parallelismThreshold, BiFunction<? super K,? super V,? extends U> transformer, Consumer<? super U> action)
    

I get why we have function, bifunction, consumer, biconsumer, and all that.
What I don't understand is why we have a special method for applying a
function before passing to the consumer. It seems like the minor convenience
in terms of syntax is outweighed by the proliferation of method signatures.

~~~
openasocket
Well, what's the alternative? In a more functional language we have two
options: have a map() function we apply first and then call forEach() on the
result, and function composition. The problem with the map() function is we
have to return a ConcurrentHashMap, which means we're effectively making a
copy of the data, which isn't space efficient. The function composition
operation is more tenable (in Java, this is the andThen() or compose()
methods, which are duals), but kind of ugly to write out if both the
transformer and action are lambda expressions. Truth be told, I don't see the
disadvantage of having extra method signatures: their usage is optional, and I
imagine they have default implementations that refer to each other using
function composition on the back end.

There's also one annoyance that neither function composition nor extra method
signatures solve, which is boxing primitives. The transformer has to return an
object, and the action has to accept one, so the example forEach(4,
List::size, ...) will generate a bunch of boxed integers. Hopefully the JIT
will notice this and try to elide the allocations, but I don't know how much
you can trust the JIT to figure that out. The best thing to do is to manually
compose the operations yourself, but that isn't always possible or convenient.

~~~
hyperpape
When I look at the documentation, I find the section with forEach to be so
much noise. Ditto for autocompletion in an IDE.
[https://docs.oracle.com/javase/8/docs/api/java/util/concurre...](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html).

When does manual composition not work well? The article's examples seem easy
enough:

    
    
        map.forEach(1, (k, v) -> "There is " + v.size() + " articles about " + k, System.out::println);
        map.forEach(1, (k, v) -> System.out.println("There is " + v.size() + " articles about " + k));

~~~
openasocket
To be fair, there are only 3 signatures for the forEach() method, the reason
that section is so noisy is because it's followed by forEachEntry(),
forEachKey(), and forEachValue(), each of which has two signatures. You
technically don't need those other methods, but they are convenient to have. I
find "map.forEachValue(1, Foo::bar)" to be more clear than "map.forEach(1, (_,
v) -> Foo.bar(v))".

Manual composition can get ugly if the argument is used multiple times,
especially if the function is non trivial. Say something like

    
    
       map.forEach(1, Expensive::func, x -> x * x)
    

which would naively manually compose to

    
    
       map.forEach(1, (k, v) -> Expensive.func(k, v) * Expensive.func(k, v))
    

which is going to be less efficient, doubly bad if that function has side
effects, and noisy on top of it. You could assign the value to a local
variable inside the lambda, which gives you

    
    
       map.forEach(1, (k, v) -> { int x = Expensive.func(k, v); return x * x; })
    

which fixes the efficiency and correctness problems, but is a bit too verbose
for me. There's probably other situations, that was just the first one I
thought of.

Honestly, though, it all comes down to taste and the situation. I personally
would use manual composition excepting circumstances like my example above.

------
masklinn
Isn't that about 3 years late? IIRC Java 8 was released in early 2014.

~~~
Reason077
The Java world tends to be pretty conservative and slow-moving compared to
newer, trendier languages.

So while it's been out for some time, a lot of enterprise code bases are only
just now starting to move to Java 8, and developers are only now really
getting their teeth into these sorts of features.

Also, Java 8 was a pretty huge release. Even experienced developers are no
doubt discovering little new interesting features and tricks all the time.

~~~
throwawayish
> The Java world tends to be pretty conservative and slow-moving compared to
> newer, trendier languages.

newer, trendier languages like Python, Ruby or C++? ;)

~~~
izacus
He who uses Python 3 / C++14 in production may cast the first stone :P

~~~
bhaak
I think there's a reason why you didn't mention Ruby 2.4.0. :-)

~~~
izacus
Just the fact I don't know enough about it to find an example :D

------
kgdinesh
My favourite is the fact that ConcurrentHashMap does not de-generate into a
Linked List anymore.

~~~
xxs
oh well, unless you had specifically designed custom made hash function (like
hashCode() {return 1;}) it never did.

And even now it still does it do so... unless you do it on purpose but
implement properly j.u.Comparable.

------
LgWoodenBadger
Most of the methods mentioned in the article seem half-assed to me, especially
when compared to the "natural" Java 8 functionality that's commonly available
on Streams, particularly .map() and .filter().

Maybe there are optimizations, but why would you have a custom signature to
add a transformer (really it's just a map()) to forEach(), when you would
normally just .map().forEach()?

~~~
JackFr
> why would you have a custom signature to add a transformer really it's just
> a map()) to forEach()

Might have to do with the parallelism. With the transformer in the forEach
call, the transformer will (presumably) be on the same thread as the consumer.

(That's just a guess--I haven't actually looked at code)

------
corsibu
For what I care, the feature in itself it is not new, but I am happy to find
out things in the JVM space that I haven't known about before. Especially
since Java 8 came as one of the biggest releases to date in terms of new
features and functionality added to the language.

------
cygned

        > .search()
    

Why didn't they name the function "filter", like everyone else does?

~~~
hyperpape
I think because it just returns a single value:
[https://docs.oracle.com/javase/8/docs/api/java/util/concurre...](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html)

~~~
nilved
Why didn't they name the function "find", like everyone else does?

