Every man has his hobbyhorse. Like I love Java. There’s no end to learning, and that’s one of the reasons I like it. The tools you use in your daily work often have something you have never known about, such as a method or an interesting use. Take threads. Yes, threads. Or rather, the Thread class. When building highly scalable systems, we often face all kinds of concurrent programming problems, but what we’re going to cover here may be slightly different.
In this article you’ll see some of the less common methods and techniques that threads provide. Whether you’re a beginner, an advanced user, or a Java expert, you want to take a look at what you already know and what you just learned. If you think there’s anything else worth sharing about threads, please feel free to comment below. Let’s get started.
A beginner
1. The thread’s name
Each thread in the program has a name, and it is assigned a simple Java string as the thread name when it is created. The default names are “thread-0”, “thread-1”, “thread-2”, and so on. Now the interesting thing is that Thread provides two ways to set Thread names:
Thread constructor, here is the simplest implementation:
class SuchThread extends Thread {
Public void run() {
System.out.println (“Hi Mom! ” + getName());
}
}
SuchThread wow = new SuchThread(“much-name”);
Thread name setter methods:
Wow. Elegantly-named setName (” Just another thread name “);
Yes, thread names are mutable. So we can change its name at run time instead of specifying it at initialization. The name field is just a simple string object. That is, it can be as long as 2³¹-1 characters (integer.max_value). That’s enough. Note that this name is not a unique identifier, so different threads can have the same thread name. Also, do not use null as a thread name, otherwise an exception will be thrown (of course, “null” is still ok).
Use thread names to debug problems
Now that you can set thread names, it’s easier to troubleshoot problems if you follow certain naming conventions. A name like Thread-6 seems so heartless that there has to be a better one. Append the transaction ID to the thread name when processing a user request, which can significantly reduce your troubleshooting time.
Pool-1-thread-1 #17 PRIo =5 OS_PRIO =31 TID = 0x00007F9D620C9800
nid=0x6d03 in Object.wait() [0x000000013ebcc000]
Pool-1-thread-1. That’s too serious. Let’s see what happens here, and give it a better name:
Thread.currentThread().setName(Context + TID + Params + current Time, …) ;
Now let’s run jStack again and the picture becomes clear:
“Queue Processing Thread, MessageID: AB5CAD, type:
AnalyzeGraph, queue: ACTIVE_PROD, Transaction_ID: 5678956,
Start Time: 30/12/2014 17:37 “#17 PRIO =5 OS_PRIO =31 TID = 0x00007F9D620C9800
nid=0x6d03 in Object.wait() [0x000000013ebcc000]
If we know what a thread is doing, then when it fails, we can at least get the transaction ID to start troubleshooting. You can go back to the problem, reproduce it, and then locate the problem and fix it. If you want to see some useful uses of jStack, check out this article.
- Thread priority
Another interesting property of a thread is its priority. The priority of a thread is between 1 (MINPRIORITY) and 10 (MAXPRIORITY), and the default for the main thread is 5 (NORM_PRIORITY). Each new thread inherits its parent thread’s priority by default, so all threads have priority 5 if you haven’t set it. This is a commonly overlooked attribute, and its value can be obtained and modified using the getPriority() and setPriority() methods. Thread constructors do not have this functionality.
Where are priorities used?
Of course, not all threads are created equal; some need immediate ATTENTION from the CPU, while others are just background tasks. The priority is used to tell this to the operating system’s thread scheduler. In Takipi, which is an error tracking and troubleshooting tool we developed, threads that handle user exceptions have MAX_PRIORITY, while threads that are just reporting new deployments have lower priority. You might expect higher-priority threads to get more time from the JVM’s thread scheduler. But that’s not always the case.
At the operating system level, each new thread corresponds to a local thread, and the priority of the Java thread you set is translated into the priority of the local thread, which varies from platform to platform. On Linux, you can turn on the ‘-xx :+UseThreadPriorities’ option to enable this feature. As mentioned earlier, thread priority is just a suggestion you provide. Java thread priorities do not cover the full spectrum of priorities compared to Linux native priorities (Linux has 1 to 99 priorities, and threads have priorities ranging from -20 to 20). The biggest benefit is that the priority you set can be reflected in the CPU time per thread, but relying solely on thread priority is not recommended.
Advanced article
3. Thread local storage
This is slightly different from the previous two. ThreadLocal is implemented outside of the Thread class (java.lang.threadLocal), but it stores a unique piece of data for each Thread. As its name suggests, it provides local storage for threads, which means that the variables you create are unique to each thread instance. Like Thread names and Thread priorities, you can customize attributes as if they were stored inside Thread threads. Cool? But before you get too excited, there are a few ugly things to be said.
There are two recommended ways to create ThreadLocal: either as static variables or as properties in a singleton instance, which can be non-static. Note that its scope is global, only as if it were local to the thread accessing it. In the following example, ThreadLocal stores a data structure that can be easily accessed:
public static class CriticalData
{
public int transactionId;
public int username;
}
public static final ThreadLocal globalData =
new ThreadLocal();
Once you have a ThreadLocal object, you can manipulate it with the globaldata.set () and globalData.get() methods.
Global variables? That’s not a good thing, right
Also necessarily. ThreadLocal can be used to store transaction ids. This is useful if an uncaught exception occurs in your code. The best practice is to set up an UncaughtExceptionHandler, which is supported by the Thread class itself, but you have to implement the interface yourself. Once the UncaughtExceptionHandler is executed, there is almost no clue as to what is going on. Now all you can get is the Thread object, and all the variables that caused the exception are no longer accessible because the stack frames have been ejected. Once it reaches UncaughtExceptionHandler, the thread is in its last gasp, and the ThreadLocal is the last straw it can grab.
Let’s try this:
System.err.println(“Transaction ID ” + globalData.get().transactionId);
We can store some valuable contextual information related to the error. A more creative use of ThreadLocal is to use it to allocate a specific chunk of memory so that a worker thread can use it continuously as a cache. Of course, whether this works or not depends on how you weigh CPU versus memory. Yes, the concern with ThreadLocal is that it wastes memory space. As long as the thread is alive, it will always be there, and it won’t be reclaimed unless you release it. So if you’re using it you better be careful and keep it simple.
- User thread and daemon thread
Let’s return to the Thread class. Each thread in the program has a state, either a user state or a daemon state. In other words, either a foreground thread or a background thread. The main thread is the user thread by default, and each new thread inherits thread state from the thread that created it. So if you set up a thread as a daemon thread, all the threads it creates will be marked as daemons. If all threads in a program are daemons, the process terminates. We can view and set thread status using boolea.setdaemon (true) and.isdaemon () methods.
When will daemon threads be used?
If a process does not have to wait for a thread to terminate, that thread can be set up as a daemon thread. This saves the hassle of normally closing a thread and allows it to be terminated immediately. On the other hand, if a thread that is performing an operation must be closed properly or there will be bad consequences, then it should be a user thread. These are usually critical transactions, such as database entries or updates, that cannot be interrupted.
expert
- Processor Affinity
This is going to be closer to hardware, that is, when software meets hardware. Processor affinity allows you to bind threads or processes to a specific CPU core. This means that as long as it is a particular thread, it must only execute on a particular CPU core. It is usually up to the operating system’s thread scheduler to decide how to bind according to its own logic, which probably takes into account the thread priorities we mentioned earlier.
The benefit of this is the CPU cache. If a thread is only running on one core, the probability that its data happens to be in the cache is greatly increased. If the data happens to be in the CPU cache, there is no need to reload it from memory. Those milliseconds you save can be put to good use, as your code can start executing immediately and make better use of the CPU time allocated to it. Of course, there may be some optimization at the operating system level, and the hardware architecture is certainly an important factor, but taking advantage of processor affinity at least reduces the chance of threads switching cpus.
Since there are so many factors involved, it’s best to test how much processor affinity affects throughput. This approach may not always improve performance significantly, but at least it has the benefit of relatively stable throughput. Affinity strategies can be refined to very fine granularity, depending on what you want. The high-frequency trading industry is one of the places where this strategy works best.
Processor affinity test
Java has no native support for processor affinity, and of course, the story doesn’t end there. On Linux, you can use the taskset command to set process affinity. Suppose we now have a Java process running and we want to bind it to a specific CPU:
Taskset -C 1 “Java AboutToBePinned”
If it is an already running process:
taskset -c 1
It takes a little more code to get down to the thread level. Fortunately, there is an open source library that does just that: Java-thread-affinity. The library was developed by Peter Lawrey of OpenHFT and is probably the easiest and most straightforward way to implement this functionality. Let’s take a quick look at how to bind a thread with an example. For more details on the library, see its documentation on Github:
AffinityLock al = AffinityLock.acquireLock();
That will do. Some of the more advanced options for acquiring locks — such as selecting cpus based on different policies — are explained in detail on Github.
conclusion
In this article we introduced five things about threads: thread names, thread-local storage, priority, daemon threads, and processor affinity. I hope this opens a new window on what you use in your daily work and look forward to your feedback! Is there any thread handling method you can share with us? Please feel free to comment.
Finally, to share a bit of welfare, I also have sorted out a set of Java documentation, the content of this article is also included in it, please pay attention to this headline number, forward after the private letter: Java interview can be obtained! Thank you very much for your support