[#2761] Proper work-around for data-corruption caused by cached ByteBuffers

Motivation:

The previous fix did disable the caching of ByteBuffers completely which can cause performance regressions. This fix makes sure we use nioBuffers() for all writes in NioSocketChannel and so prevent data-corruptions. This is still kind of a workaround which will be replaced by a more fundamental fix later.

Modifications:

- Revert 4059c9f3549753119576a287492dd70ae4742988
- Use nioBuffers() for all writes to prevent data-corruption

Result:

No more data-corruption but still retain the original speed.
Pluggable algorithm to choose next EventLoop

Motivation:

Currently when a new event loop is needed to register a channel our EventLoopGroup implementations just
use a round-robin like algorithm to choose the next event loop. Unfortunately this is not always good
enough as different event loops may become more busy than others over time. This is especially true when
an application handles different kinds of connections (long-living and short-living). This change allows
developers to plug in their own algorithms to choose the next event loop and to collect metrics about the
busyness of the event loops. See #1230

Modifications:

- TODO

Result:

- Developers can plug in their own algorithms into MultithreadEventLoopGroup constructors.
- Developers can gather metrics/feedback from the event loops and use the data in their custom algorithms.