1. Overview
1.概述
In this article, we’ll go over a Java thread state — specifically, Thread.State.WAITING. We’ll look at the methods with which a thread enters this state and the differences between them. Finally, we’ll take a closer look at the LockSupport class, which offers several static utility methods for synchronization.
在这篇文章中,我们将讨论Java的线程状态–具体而言,Thread.State.WAITING。我们将看看线程进入这种状态的方法以及它们之间的区别。最后,我们将仔细看看LockSupport类,它提供了几个用于同步的静态实用方法。
2. Entering Thread.State.WAITING
2.进入Thread.State.WAITING
Java provides multiple ways to put a thread in the WAITING state.
Java提供了多种方法来使线程处于WAITING状态。
2.1. Object.wait()
2.1.Object.wait()
One of the most standard ways we can put a thread in the WAITING state is through the wait() method. When a thread owns an object’s monitor, we can pause its execution until another thread has completed some work and wakes it up using the notify() method. While execution is paused, the thread is in the WAITING (on object monitor) state, which is also reported in the program’s thread dump:
我们将线程置于WAITING状态的最标准方式之一是通过wait()方法。当一个线程拥有一个对象的监视器时,我们可以暂停它的执行,直到另一个线程完成一些工作并使用notify()方法唤醒它。当执行被暂停时,该线程处于WAITING (on object monitor)状态,这在程序的线程转储中也会被报告。
"WAITING-THREAD" #11 prio=5 os_prio=0 tid=0x000000001d6ff800 nid=0x544 in Object.wait() [0x000000001de4f000]
java.lang.Thread.State: WAITING (on object monitor)
2.2. Thread.join()
2.2.Thread.join()
Another method we can use to pause a thread’s execution is through the join() call. When our main thread needs to wait for a worker thread to finish first, we can call join() on the worker thread instance from the main thread. Execution will be paused and the main thread will enter the WAITING state, reported from jstack as WAITING (on object monitor):
我们可以使用另一种方法来暂停线程的执行,即通过join()调用。当我们的主线程需要先等待一个工作线程完成时,我们可以从主线程中调用join()于工作线程实例。执行将被暂停,主线程将进入WAITING状态,从jstack报告为WAITING(对象监视器上)。
"MAIN-THREAD" #12 prio=5 os_prio=0 tid=0x000000001da4f000 nid=0x25f4 in Object.wait() [0x000000001e28e000]
java.lang.Thread.State: WAITING (on object monitor)
2.3. LockSupport.park()
2.3.LockSupport.park()
Finally, we can also set a thread to the WAITING state with the park() static method of the LockSupport class. Calling park() will stop execution for the current thread and put it into the WAITING state — and more specifically, the WAITING (parking) state as the jstack report will show:
最后,我们还可以通过LockSupport类的park()静态方法将一个线程设定为WAITING状态。调用park()将停止当前线程的执行,并将其置于WAITING状态–更具体地说,是WAITING (parking)状态,正如jstack报告所显示的。
"PARKED-THREAD" #11 prio=5 os_prio=0 tid=0x000000001e226800 nid=0x43cc waiting on condition [0x000000001e95f000]
java.lang.Thread.State: WAITING (parking)
Since we want to better understand thread parking and unparking, let’s take a closer look at how this works.
既然我们想更好地了解线程停车和不停车,让我们仔细看看这是如何进行的。
3. Parking and Unparking Threads
3.泊车和卸车线
As we previously saw, we can park and unpark threads by using the facilities provided by the LockSupport class. This class is a wrapper of the Unsafe class, and most of its methods immediately delegate to it. However, since Unsafe is considered an internal Java API and shouldn’t be used, LockSupport is the official way we can get access to the parking utilities.
正如我们之前所看到的,我们可以通过使用LockSupport类所提供的设施来停放和卸载线程。该类是Unsafe类的封装器,其大部分方法都会立即委托给它。然而,由于Unsafe被认为是一个内部的Java API,不应该被使用,LockSupport是我们可以访问停车工具的官方途径。
3.1. How to Use LockSupport
3.1.如何使用LockSupport?
Using LockSupport is straightforward. If we want to stop a thread’s execution, we call the park() method. We don’t have to provide a reference to the thread object itself — the code stops the thread that calls it.
使用LockSupport是很直接的。如果我们想停止一个线程的执行,我们调用park()方法。我们不需要提供对线程对象本身的引用–代码会停止调用它的线程。
Let’s look at a simple parking example:
让我们看看一个简单的停车例子。
public class Application {
public static void main(String[] args) {
Thread t = new Thread(() -> {
int acc = 0;
for (int i = 1; i <= 100; i++) {
acc += i;
}
System.out.println("Work finished");
LockSupport.park();
System.out.println(acc);
});
t.setName("PARK-THREAD");
t.start();
}
}
We created a minimal console application that accumulates the numbers from 1 to 100 and prints them out. If we run it, we’ll see that it prints Work finished but not the result. This is, of course, because we call park() right before.
我们创建了一个最小的控制台应用程序,将1到100的数字累积起来并打印出来。如果我们运行它,我们会看到它打印了工作完成,但没有打印出结果。当然,这是因为我们在之前就调用了park()。
To let the PARK-THREAD finish, we must unpark it. To do this, we have to use a different thread. We can use the main thread (the thread running the main() method) or create a new one.
为了让PARK-THREAD完成,我们必须取消停放它。要做到这一点,我们必须使用一个不同的线程。我们可以使用main线程(运行main()方法的线程)或创建一个新线程。
For simplicity, let’s use the main thread:
为了简单起见,让我们使用main线程。
t.setName("PARK-THREAD");
t.start();
Thread.sleep(1000);
LockSupport.unpark(t);
We add a one-second sleep in the main thread to let the PARK-THREAD finish the accumulation and park itself. After that, we unpark it by calling unpark(Thread). As expected, during unparking, we must provide a reference to the thread object that we want to start.
我们在main线程中添加一秒钟的睡眠,让PARK-THREAD完成积累并停放自己。之后,我们通过调用unpark(Thread)将其解停。正如预期的那样,在取消停放期间,我们必须提供一个对我们想要启动的线程对象的引用。
With our changes, the program now properly terminates and prints the result, 5050.
经过我们的修改,现在程序正确终止并打印出结果,5050。
3.2. Unpark Permits
3.2.取消停车许可
The internals of the parking API work by using a permit. This, in practice, works like a single permit Semaphore. The park permit is used internally to manage the thread’s state with the park() method consuming it, while unpark() makes it available.
停车API的内部结构是通过使用一个许可证来工作的。在实践中,这就像一个单一的许可证Semaphore。停放许可在内部被用来管理线程的状态,park()方法消耗它,而unpark()使它可用。
Since we can only have one permit available per thread, calling the unpark() method multiple times has no effect. A single park() call will disable the thread.
由于我们每个线程只能有一个许可证,多次调用unpark()方法没有任何效果。一次park()调用将禁用该线程。
What is interesting, however, is that the parked thread waits for a permit to become available to enable itself again. If a permit is already available when calling park(), then the thread is never disabled. The permit is consumed, the park() call returns immediately, and the thread continues execution.
然而,有趣的是,被停放的线程会等待一个可用的许可来再次启用自己。如果在调用park()时已经有一个许可证,那么该线程永远不会被禁用。许可证被消耗,park()调用立即返回,该线程继续执行。
We can see this effect by removing the call to sleep() in the previous code snippet:
我们可以通过删除前面代码段中对sleep()的调用来看到这种效果。
//Thread.sleep(1000);
LockSupport.unpark(t);
If we run our program again, we’ll see that there’s no delay in the PARK-THREAD execution. This is because we call unpark() immediately, which makes the permit available for park().
如果我们再次运行我们的程序,我们会看到PARK-THREAD的执行没有延迟。这是因为我们立即调用unpark(),这使得许可可以用于park()。
3.3. Park Overloads
3.3.园区超载
The LockSupport class contains the park(Object blocker) overload method. The blocker argument is the synchronization object that is responsible for thread parking. The object we provide doesn’t affect that parking process, but it’s reported in the thread dump, which could help us diagnose concurrency issues.
LockSupport类包含park(Object blocker)重载方法。blocker参数是负责线程停放的同步对象。我们提供的对象并不影响该停放过程,但它会在线程转储中被报告,这可以帮助我们诊断出并发性问题。
Let’s change our code to contain a synchronizer object:
让我们改变我们的代码,包含一个同步器对象。
Object syncObj = new Object();
LockSupport.park(syncObj);
If we remove the call to unpark() and run the application again, it will hang. If we use jstack to see what the PARK-THREAD is doing, we’ll get:
如果我们删除对unpark()的调用并再次运行该应用程序,它将挂起。如果我们使用jstack来查看PARK-THREAD正在做什么,我们会得到。
"PARK-THREAD" #11 prio=5 os_prio=0 tid=0x000000001e401000 nid=0xfb0 waiting on condition [0x000000001eb4f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b4a8690> (a java.lang.Object)
As we can see, the last line contains which object the PARK-THREAD is waiting for. This is helpful for debugging purposes, which is why we should prefer the park(Object) overload.
我们可以看到,最后一行包含了PARK-THREAD正在等待的对象。这对调试是有帮助的,这就是为什么我们应该更喜欢park(Object)重载。
4. Parking vs. Waiting
4.停车与等待
Since both of these APIs give us similar functionality, which should we prefer? In general, the LockSupport class and its facilities are considered low-level API. Additionally, the API can be easily misused, leading to obscure deadlocks. For most cases, we should use the Thread class’s wait() and join().
既然这两个API都给了我们类似的功能,那么我们应该选择哪一个呢?一般来说,LockSupport类及其设施被认为是低级别的API。此外,该API很容易被误用,导致不明显的死锁。对于大多数情况,我们应该使用Thread类的wait()和join()。
The benefit of using parking is that we don’t need to enter a synchronized block to disable the thread. This is important because synchronized blocks establish a happens-before relationship in the code, which forces a refresh of all the variables and can potentially lower performance if it’s not needed. This optimization, however, should rarely come into play.
使用停放的好处是,我们不需要进入synchronized块来禁用该线程。这一点很重要,因为synchronized块在代码中建立了happens-before关系,这迫使我们刷新所有的变量,如果不需要的话,有可能会降低性能。然而,这种优化应该很少发挥作用。
5. Conclusion
5.总结
In this article, we explored the LockSupport class and its parking API. We looked at how we can use it to disable threads and explained how it works, internally. Finally, we compared it to the more common wait()/join() API and showcased their differences.
在这篇文章中,我们探讨了LockSupport类和它的停车API。我们研究了如何使用它来禁用线程,并解释了它的内部工作方式。最后,我们将其与更常见的wait()/join() API进行了比较,并展示了它们的区别。
As always, the code examples can be found over on GitHub.
一如既往,代码示例可以在GitHub上找到。