Back in 2018, fintech experts shared an insider that at the beginning of the tenth, the profitability of electronic exchange trading was $ 1 with $ 1000 of invested money. By 2018, it had dropped to 1 cent from $1,000. Obviously, now the profit is even less. The decision on the transaction must be made very quickly, every microsecond is worth its weight in gold. Fintech chooses Java. So everything is fine with the speed of us Java programmers.
The question “Why is Java slowing down then?” is more about features.
For example, about the banal String. repeat(X). It’s hard to imagine, but back in 2009, the task “How to repeat a string?” on StackOverflow collected 720 likes and has several original solutions. Java 11 put an end to it by adding the desired method to the Standard Library.
Well, how did you put it? At the beginning of 2022, only a third of projects use Java version 11 and higher, and the rest use older versions of the JVM in the prod, and the task is not outdated for them.
“Just think, String. repeat(Y), what nonsense!” — you will say. Probably, you did not come across the task of implementing the logic of String. repeat(Z) at the interview.
That’s not all right with asynchronous programming in Java either. And this is no longer a trifle. The fact is that Moore’s law no longer works. In the original, Moore’s observation sounds like “the number of transistors in a chip doubles every year.” This was the case from 1975 to about 2010, and after the doubling ended.
Previously, a programmer, if his project was not moving fast enough, could take a break for a year or two, buy new equipment and achieve the required performance indicators.
Such tricks don’t work anymore. To increase performance, you have to use several processors at the same time. Here we have Amdahl’s law. On the contrary, no one canceled it. It is very difficult to parallelize the work of the program. It turns out that some part of the calculations is always essentially sequential, and some can be performed on several cores simultaneously. The acceleration from running on multiple processors strongly depends on the ratios of these parts.
We are even forced to distribute tasks between different machines, and then inevitably one starts waiting for the other. In both local and global parallel computations, the results have to wait asynchronously. Asynchronous programming is our present and at least the near future. In classic Java, this future is sad. Blocking programming for a large number of threads rests on resources. The words then Apply, then CombineAsync, exceptionally resonate with horror in the heart of anyone who has dealt with non-blocking Core Java.
Alternatives to Java. What are they like?
Alternative Java programming languages have been introduced into the JDK ecosystem for a long time. For example, Scala appeared in 2003 or Clojure in 2007. What can we say about these languages? A lot of good things.
According to some reports, a Clojure specialist receives twice as much as a Java programmer, similarly (one and a half times) – a Scala specialist. However, the labor market for these languages is smaller than that of Java. There is no need to talk about any serious competition on their part. Most likely, the use of such languages is niche and is associated with high parallelism.
In 2016, the 1.0 version of Kotlin is being released. By now, being younger than Java, Kotlin has received one-fourth of its share in the labor market. In the niche of Android applications, Kotlin has even become a de facto standard. Why did this happen?
Firstly, Kotlin has special libraries that facilitate asynchronous programming. These are the so-called Kotlin coroutines. They allow you to operate with something like lightweight threads. Lightweight here means cheap in the sense of allocating resources per instance.
Secondly, Kotlin asynchronous constructs are easier to interface with each other than the puzzling interfaces of non-blocking Java.
Thirdly, the Kotlinovsky Korutin library implements the concept of structural competitiveness.
The term “Structural competitiveness” was coined by Martin Sustrik, the creator of ZeroMQ. Following him, Nathaniel Smith brilliantly revealed the concept in his “Notes on Structural Competitiveness”. At the moment there are several implementations of this approach for a variety of programming languages. Among them are libdill (C), Trio (Python), async_nursery (Rust), Venice (Swift) and, of course, Kotlin’s coroutines.
The principle of structural competitiveness allows you to correctly handle errors. In particular, to free up limited resources: to close file descriptors, sockets, and so on. We don’t want applications in a mobile device to “eat up” memory, resources during operation and finally “slow down” the device? So Google doesn’t want that either. This is how Kotlin gained primacy over Java on Android.
A little bit about the Loom project
We return to Java and the topic of lightweight threads. It is planned to add a feature to the language, which is currently being developed. This is a Loom project, the first public release of which took place back in 2018. But the project flew past the 17th LTS release of JDK, and past the passing 18th release. Judging by the pace at which new commits are being thrown into the project repository right now, Loom may not be in the 19th release.
This means that Kotlin’s coroutines have gained a head start in order to consolidate their position in the market and become the de facto standard in this area as well. Those who needed lightweight threads have already chosen Kotlin.
There is another negative factor for Loom: at the first stage, there are no plans to introduce structural competitiveness in it. That is, the battle for Android is not included in the plans of Java language builders.
The second factor that hinders the construction of distributed systems based on Loom is the lack of reactive libraries at the start that would support the concept of back pressure.
Briefly about reactivity. If in the classical model of client-server interaction, the client regulates the rate at which it sends data, then in the reactive model, the server plays the role of controller. The consumer works the same way as we drink tea: you need to drink from a cup so much that you can swallow. In the classical interaction model, the server can choke.
Examples of the implementation of reactive interaction are Project React and Kotlin Coroutines. The Spring framework uses them interchangeably. It will take some time to create a similar library under Loom. And this will further delay the introduction of the new feature.
It would be appropriate to quote the words of James Gosling, the creator of Java, said outside the public part of the 2020 interview: “At first we were afraid of C#, but when we realized that all it could do was copy Java, our fears dissipated.” Well, C# has grown a lot since its inception. The number of its users is already comparable to the Java audience, and salaries and satisfaction from using the language seem to be higher.
For ten years now, async/await constructs have been added to C#, which open up the possibility of writing sane asynchronous code. Except that in terms of the number of artifacts of various open libraries (6 times), Microsoft’s development is significantly inferior to Gosling’s brainchild. And in a broader sense — the entire ecosystem, because libraries can be used from Closure, Scala, Kotlin and other JVM languages.
The importance of backward compatibility support for language development cannot be overestimated. At the same time, it seems that the developers of the language are aiming for more. We see how new semantics are being added to the language in a backward compatible way.
If you take a specific Apache Tomcat and, without changing the source code, build it using the Loom prototype, then the web server will show unprecedented performance. It seems that backward compatibility is already becoming a fetish. It slows down both the development of the language and the ecosystem as a whole. Java should not be slowed down so much.