JavaRush /Java Blog /Random EN /Coffee break #113. 5 things you probably didn't know abou...

Coffee break #113. 5 things you probably didn't know about multithreading in Java. 10 JetBrains Extensions to Fight Technical Debt

Published in the Random EN group

5 Things You Probably Didn't Know About Multithreading in Java

Source: DZone Thread is the heart of the Java programming language. Even running the Hello World program needs the main thread. If necessary, we can add other threads to the program if we want our application code to be more functional and performant. If we are talking about a web server, then it simultaneously processes hundreds of requests simultaneously. Multiple threads are used for this. Coffee break #113.  5 things you probably didn't know about multithreading in Java.  10 JetBrains Extensions to Combat Technical Debt - 1Threads are undoubtedly useful, but working with them can be difficult for many developers. In this article, I'll share five multithreading concepts that new and experienced developers may not know about.

1. Program order and execution order do not match

When we write code, we assume that it will execute exactly the way we write it. However, in reality this is not the case. The Java compiler may change the order of execution to optimize it if it can determine that the output will not change in single-threaded code. Look at the following code snippet:

package ca.bazlur.playground;

import java.util.concurrent.Phaser;

public class ExecutionOrderDemo {
    private static class A {
        int x = 0;
    }

    private static final A sharedData1 = new A();
    private static final A sharedData2 = new A();

    public static void main(String[] args) {
        var phaser = new Phaser(3);
        var t1 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l1 = sharedData1;
            var l2 = l1.x;
            var l3 = sharedData2;
            var l4 = l3.x;
            var l5 = l1.x;
            System.out.println("Thread 1: " + l2 + "," + l4 + "," + l5);
        });
        var t2 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l6 = sharedData1;
            l6.x = 3;
            System.out.println("Thread 2: " + l6.x);
        });
        t1.start();
        t2.start();
        phaser.arriveAndDeregister();
    }
}
This code seems simple. We have two shared data instances ( sharedData1 and sharedData2 ) that use two threads. When we execute the code, we expect the output to be like this:
Thread 2: 3 Thread 1: 0,0,0
But if you run the code several times, you will see a different result:
Thread 2: 3 Thread 1: 3,0,3 Thread 2: 3 Thread 1: 0,0,3 Thread 2: 3 Thread 1: 3,3,3 Thread 2: 3 Thread 1: 0,3,0 Thread 2 : 3 Thread 1: 0,3,3
I'm not saying that all of these streams will play exactly like this on your machine, but it's entirely possible.

2. The number of Java threads is limited

Creating a thread in Java is easy. However, this does not mean that we can create as many of them as we like. The number of threads is limited. We can easily find out how many threads we can create on a particular machine using the following program:

package ca.bazlur.playground;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Playground {
    public static void main(String[] args) {
        var counter = new AtomicInteger();
        while (true) {
            new Thread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            }).start();
        }
    }
}
The above program is very simple. It creates a thread in a loop and then parks it, which means the thread is disabled for future use but performs a system call and allocates memory. The program continues to create threads until it can create no more, and then throws an exception. We are interested in the number that we will receive until the program throws an exception. On my computer I was only able to create 4065 threads.

3. Too many threads does not guarantee better performance

It is naive to believe that making threads easy in Java will improve application performance. Unfortunately, this assumption is wrong with our traditional multithreading model that Java provides today. In fact, too many threads can reduce the performance of an application. Let's first ask this question: what is the optimal maximum number of threads we can create to maximize application performance? Well, the answer is not that simple. It very much depends on the type of work we do. If we have multiple independent tasks, all of which are computational and not blocking any external resources, then having a large number of threads will not improve performance much. On the other hand, if we have an 8-core processor, the optimal number of threads could be (8 + 1). In such a case, we can rely on parallel thread introduced in Java 8. By default, parallel thread uses the shared Fork/Join pool. It creates threads equal to the number of available processors, which is enough for them to work intensively. Adding more threads to a CPU-intensive job where nothing is blocked will not improve performance. Rather, we will simply waste resources. Note. The reason for having an extra thread is that even a compute-intensive thread sometimes causes a page fault or is suspended for some other reason. (See: Java Parallelism in Practice , Brian Goetz, page 170) However, suppose, for example, that the tasks are I/O bound. In this case, they depend on external communication (e.g. database, other APIs), so a larger number of threads makes sense. The reason is that when a thread is waiting on the Rest API, other threads can continue working. Now we can ask again, how many threads are too many for such a case? Depends. There are no perfect numbers that fit all cases. Therefore, we must do adequate testing to find out what works best for our specific workload and application. In the most typical scenario, we usually have a mixed set of tasks. And in such cases things go to completion. In his book “Java Concurrency in Practice,” Brian Goetz proposed a formula that we can use in most cases. Number of threads = Number of Available Cores * (1 + Wait time / Service time) Waiting time can be IO, such as waiting for an HTTP response, acquiring a lock, and so on. Service Time(Service time) is the computation time, such as processing an HTTP response, marshaling/unmarshaling, and so on. For example, an application calls an API and then processes it. If we have 8 processors on the application server, the average API response time is 100ms, and the response processing time is 20ms, then the ideal thread size would be:
N = 8 * ( 1 + 100/20) = 48
However, this is an oversimplification; adequate testing is always critical to determining the number.

4. Multithreading is not parallelism

Sometimes we use multithreading and parallelism interchangeably, but this is no longer entirely relevant. Although in Java we achieve both using a thread, they are two different things. “In programming, multithreading is a special case regardless of the processes running, and parallelism is the simultaneous execution of (possibly related) calculations. Multithreading is about interacting with a lot of things at the same time. Concurrency is doing many things at the same time.” The above definition given by Rob Pike is quite accurate. Let's say we have completely independent tasks, and they can be calculated separately. In this case, these tasks are called parallel and can be executed with a Fork/Join pool or a parallel thread. On the other hand, if we have many tasks, some of them may depend on others. The way we compose and structure is called multithreading. It has to do with structure. We may want to perform several tasks simultaneously to achieve a certain result, without necessarily finishing one faster.

5. Project Loom allows us to create millions of threads

In the previous point, I argued that having more threads does not mean improved application performance. However, in the era of microservices, we interact with too many services to get any specific work done. In such a scenario, threads remain in a blocked state most of the time. While a modern OS can handle millions of open sockets, we cannot open many communication channels since we are limited by the number of threads. But what if you create millions of threads, and each of them uses an open socket to communicate with the outside world? This will definitely improve our application throughput. To support this idea, there is an initiative in Java called Project Loom. Using it, we can create millions of virtual threads. For example, using the following code snippet, I was able to create 4.5 million threads on my machine.

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) {
        var counter = new AtomicInteger();       

        // 4_576_279 
        while (true) {
            Thread.startVirtualThread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            });
        }
    }
}
To run this program you must have Java 18 installed, which can be downloaded here . You can run the code using the following command: java --source 18 --enable-preview Main.java

10 JetBrains Extensions to Fight Technical Debt

Source: DZone Many development teams feel enormous pressure to meet deadlines. Because of this, they often don't have enough time to fix and clean up their codebase. Sometimes in these situations, technical debt quickly accumulates. Editor extensions can help solve this problem. Let's take a look at the 10 best JetBrains extensions to combat technical debt (with Java support). Coffee break #113.  5 things you probably didn't know about multithreading in Java.  10 JetBrains Extensions to Fight Technical Debt - 2

Refactoring and technical debt tools

1. RefactorInsight

RefactorInsight improves the visibility of code changes in the IDE by providing information about refactorings.
  1. The extension defines refactorings in merge requests.
  2. Marks commits that contain refactorings.
  3. Helps in viewing the refactoring of any specific commit selected in the Git Log tab.
  4. Shows the refactoring history of classes, methods, and fields.

2. Stepsize Issue Tracker in IDE

Stepsize is a great issue tracker for developers. The extension helps engineers not only create better TODOs and code comments, but also prioritize technical debt, refactorings, and the like:
  1. Stepsize lets you create and view tasks in code right in the editor.
  2. Find issues that affect the features you are working on.
  3. Add issues to your sprints using Jira, Asana, Linear, Azure DevOps, and GitHub integrations.

3. New Relic CodeStream

New Relic CodeStream is a developer collaboration platform for discussing and reviewing code. It supports pull requests from GitHub, BitBucket and GitLab, issue management from Jira, Trello, Asana and 9 others, and provides code discussions, tying it all together.
  1. Create, review, and merge pull requests in GitHub.
  2. Get feedback on work in progress with preliminary code reviews.
  3. Discussing code problems with teammates.

TODO and comments

4. Comments Highlighter

This plugin allows you to create custom highlighting of comment lines and language keywords. The plugin also has the ability to define custom tokens for highlighting comment lines.

5. Better Comments

The Better Comments extension helps you create clearer comments in your code. With this extension you will be able to classify your annotations into:
  1. Alerts.
  2. Requests.
  3. TODO.
  4. Basic moments.

Bugs and security vulnerabilities

6.SonarLint _

SonarLint allows you to troubleshoot code problems before they arise. It can also be used as a spell checker. SonarLint highlights bugs and security vulnerabilities as you code, with clear remediation instructions so you can fix them before the code is committed.

7. SpotBugs

The SpotBugs plugin provides static bytecode analysis to find bugs in Java code from IntelliJ IDEA. SpotBugs is a defect detection tool for Java that uses static analysis to find over 400 bug patterns such as null pointer dereferences, infinite recursive loops, misuse of Java libraries, and deadlocks. SpotBugs can identify hundreds of serious defects in large applications (typically about 1 defect per 1000-2000 lines of raw uncommented statements).

8. Snyk Vulnerability Scanner

Snyk's Vulnerability Scanner helps you find and fix security vulnerabilities and code quality issues in your projects.
  1. Finding and fixing security problems.
  2. View a list of different types of problems, divided into categories.
  3. Displays troubleshooting tips.

9. Zero Width Characters Locator

This plugin enhances the checking and detection of hard-to-find errors related to invisible zero width characters in source code and resources. When using, make sure that the “Zero width unicode character” check is enabled.

10.CodeMR _

CodeMR is a software quality and static code analysis tool that helps software companies develop better code and programs. CodeMR visualizes code metrics and high-level quality attributes (coupling, complexity, cohesion, and size) in various views such as Package Structure, TreeMap, Sunburst, Dependency, and Graph Views.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION