> especially when you have to unlearn sound and proven practices, which Go often requires to do.
Such as? I'm not really sure what "sound and proven practices" a Java developer would have to "unlearn" to adopt Go. Most of the differences between Go and Java amount to removing features that 20 years of Java experience have proved to be unsound or unnecessary (inheritance and exceptions, for example). From a feature perspective, Go is mostly a subset of Java. The features Go adds are mostly related to concurrency, and I've not heard anyone say Java does concurrency better than Go.
Maybe if you are taking Java the language in isolation but if you consider the JVM ecosystem than I'll absolutely say the JVM does concurrency better. While the JVM offers something like quasar (http://docs.paralleluniverse.co/quasar/) which can match go's goroutine and channels plus an actor interface or the really excellent Akka (http://akka.io/) for a non blocking async actor framework. Offering a lot more choice than golang's single concurrency pattern. I've written major applications with both go and Akka using Java (and scala) and while both were successful stable and performant it's hard to argue with the JVM's concurrency story.
Java for sure has good tools for concurrency, and you listed some of the very interesting choices!
However the flexible nature of Java concurrency also has the drawback that you might need to integrate different libraries that utilize different concurrency solutions (blocking threads, actors, non-blocking eventloops) with each other. This can end up in a lot more effort than if you try to use multiple libraries in an ecosystem with a single opinionated concurrency solution (like Go for blocking IO or node for pure nonblocking IO).
The weakest ecosystem regarding this is imho C++, where most concurrency/IO solutions only work well if you don't try to also use other solutions in parallel (e.g. QT plus glib plus boost asio plus libuv...).
I'll say that Java doesn't do currency worse than Go, that's for sure. It has all of the primitives in whatever arrangement you want to put them (Javaflow and now Coroutines if you want go-I'm-sorry-coroutines, native threads if you want those, and Go channels can be implemented in maybe two dozen lines), more flexible, battle-tested abstractions (such as Akka offering you an asynchronous, message-passing actor model, which could be written in Go but seems in practice to be passed up in favor of channels), and tooling around these that I find to be head-and-shoulders better than anything Go has (like multi-threaded debugging).
I've basically (willingly or unwillingly) turned into a Ruby person over the last few years, as neither Go nor the JVM really have a ton to offer me right now, but I don't think a fair comparison of concurrency-related stuff, either in terms of tooling, libraries, or the language itself (I'd give Go this, except that you can't build an unbounded buffered channel and at that point the use of channels for what I write rapidly approaches zero), is nearly as clear as you assert.
Java's concurrency story is weak overall. Nearly all code still uses the old-style "synchronized" blocks, rather than ReentrantLock. This shouldn't be a big surprise, considering that ReentrantLock was only introduced recently. With synchronized blocks, you don't have any way of releasing the lock other than by exiting the block, which leads to some very contorted-looking code.
The fact that you can synchronize on literally any object means that your object lock is effectively part of your public API. Some other piece of code can easily grab your object, synchronize on it, and then start calling your methods, assuming that this will be atomic. And if you change to use a different lock later, it will break.
Sure you could use BlockingQueue to get some of the benefits of Go channels. But the standard library and pretty much any software you'll interact with were written before BlockingQueue existed, so they won't make use of it. You will have to fight your lonely crusade to use message passing on your own. Which in practice means that you won't be using message passing, just plain old mutexes and volatiles.
In Go, all code runs in goroutines which get multiplexed to kernel threads. In Java, nearly all code is blocking and uses an entire kernel thread. Sure you can use NIO to write an event loop-- just as long as you're careful to never, ever call a blocking function. But nearly every interesting function in Java can block. Including the DNS lookup functions Java provides.
> The fact that you can synchronize on literally any object means that your object lock is effectively part of your public API
I'd argue that the fact you can lock on any object is a strength. These days, hardly any Java developer will use synchronized on methods and instead prefer the idiom:
public class A {
private Object lock = "";
public void foo() {
synchronized(lock) {
}
}
This allows Java code to be extremely granular in what gets locked, which has enabled very powerful multithreaded constructs and libraries such as ForkJoinPool and many others described in the Java Concurrency In Practice book.
So this doesn't map to anything I see in the JVM. It's super interesting that you say it (and I want to stress I'm not calling you a liar or anything, it's just different experiences). Personally? I haven't used synchronized blocks since college (so ~2010 or so). I've been using NIO about that time. I don't feel like I'm swimming upstream using it.
Debugging is definitely a valid counter-point. Go's debugging story is rapidly evolving, but it's not particularly friendly or settled at this point.
Goroutines are subtly different than coroutines, mostly in that goroutines are not bound to the OS thread they're created on, and also that they don't need to be explicitly yielded--any I/O or lock-blocking (including reading or writing on channels) are potential interrupt points. Channels are probably also less easy than you might think, particularly the nuances around Go's `select` keyword.
I'm not sure where exactly Java stands on all of this. I've heard that the Quasar library comes close to bringing Go's concurrency tools to the JVM, but it's not widely used and any non-Quasar I/O is likely going to block your OS thread. At any rate, while I agree that Java can theoretically come very close to matching Go's concurrency story, it falls very short in practice.
> you can't build an unbounded buffered channel and at that point the use of channels for what I write rapidly approaches zero
If you're using Ruby, I'm guessing you don't need thread safety, so you probably just want a slice.
> I don't think a fair comparison of concurrency-related stuff ... is nearly as clear as you assert
Like I said, I think libraries like Quasar have a lot of potential, but they're not widely used. If it was the de-facto solution for concurrency in Java, I would agree that "concurrency in Java is no worse than in Go" (and I would have to, since Quasar's stated purpose is to bring Go-like concurrency to Java).
> goroutines are not bound to the OS thread they're created on
Neither is a coroutine in Java using the Coroutines library (Javaflow used thread-locals, but Coroutines doesn't), or a Lua-based coroutine...I'm not sure what you're driving at here?
> If you're using Ruby
Sorry, this was inartfully said. If I am using the JVM, i.e. I want to be using something where I can be bombing around on multiple threads etc., I'm probably going to want my inter-thread channels to be unbounded. BlockingQueues in Java give you that; Go channels don't.
> Neither is a coroutine in Java using the Coroutines library (Javaflow used thread-locals, but Coroutines doesn't), or a Lua-based coroutine...I'm not sure what you're driving at here?
I'm not familiar with Coroutines or Lua-based coroutines; coroutines are almost always bound to the thread on which they're created, I was pointing out that this is a primary difference between goroutines and coroutines--goroutines are M:N threads.
> BlockingQueues in Java give you that; Go channels don't.
Go channels aren't meant for this purpose; they're synchronization primitives, not dynamic data structures. You can easily build a BlockingQueue in Go.
> coroutines are almost always bound to the thread on which they're created
Can you provide a cite for this? I'm not saying you're wrong, just that I've never heard this assertion before (and I have been doing really stupid stuff with coroutines for way too long). My understanding of a coroutine is just that it's just a cooperatively yielding function where a yield returns a continuation for later resumption.
I don't have a cite for this; almost no coroutine implementations are multiplexed across threads. I spent quite a while Googling around, and I wasn't able to come across any thing. The stuff that I did come across (without searching for 'goroutine') were comparisons of coroutines and goroutines in which one of the defining characteristics seem to be the ability (or inability, in the case of coroutines) to be multiplexed across OS threads.
In general, the term "coroutine" has come to be pretty watered down.
> ost of the differences between Go and Java amount to removing features that 20 years of Java experience have proved to be unsound or unnecessary (inheritance and exceptions, for example).
Such as? I'm not really sure what "sound and proven practices" a Java developer would have to "unlearn" to adopt Go. Most of the differences between Go and Java amount to removing features that 20 years of Java experience have proved to be unsound or unnecessary (inheritance and exceptions, for example). From a feature perspective, Go is mostly a subset of Java. The features Go adds are mostly related to concurrency, and I've not heard anyone say Java does concurrency better than Go.