Stopping a thread is a simple, but not simple, task for a goal. First, Java does not provide a direct API for stopping threads. In addition, there are some additional details to consider when stopping a thread, such as when the thread to be stopped is blocking (waiting for a lock) or waiting (waiting for another thread), there are still unfinished tasks, and so on. The two-phase termination pattern described in this article provides a common method for gracefully stopping threads.
Two-phase Termination Mode Introduction
Java does not provide a direct API for stopping threads. The two-phase termination mode breaks down the stop thread as a staging and execution phase to address possible problems during the stop thread process.
Preparation phase. The primary action in this phase is to "notify" the target thread (the thread to be stopped) to be ready to stop. This step sets a flag variable to indicate that the target thread can be ready to stop. However, because the target thread may be in a blocking state (waiting for a lock to be obtained), waiting state (such as Call object.wait), or I/O (such as Inputstream.read) waits, even if this flag is set, the target thread cannot immediately "see" This symbol and make the corresponding action. Therefore, this phase also requires the interrupt method of invoking the target thread to expect the target thread to be able to detect the method call by capturing the associated exception, thus interrupting its blocking state and waiting state. For methods that can respond to interrupt method calls (see table 1), the target thread code can detect the thread stop signal by capturing the interruptedexception thrown by these methods. However, there are some methods (such as inputstream.read) that do not respond to interrupt calls, which require us to handle manually, such as a synchronous socket I/O operation by closing the socket so that the i/ o Wait for the socket to throw java.net.SocketException.
Table 1. Some of the ways to respond to Thread.Interrupt
Method |
Response Interrupt call throws an exception |
Object.wait (), object.wait (long timeout), object.wait (long timeout, int nanos) |
Interruptedexception |
Thread.Sleep (Long Millis), Thread.Sleep (long millis, int nanos) |
Interruptedexception |
Thread.Join (), Thread.Join (Long Millis), Thread.Join (long millis, int nanos) |
Interruptedexception |
Java.util.concurrent.BlockingQueue.take () |
Interruptedexception |
Java.util.concurrent.locks.Lock.lockInterruptibly () |
Interruptedexception |
Java.nio.channels.InterruptibleChannel |
Java.nio.channels.ClosedByInterruptException |
Implementation phase. The main action of this phase is to check the thread stop flag and signal set during the preparation phase, on this basis, determine the timing of the thread to stop, and make the appropriate "cleanup" operation.
Two-phase schema for termination mode
The main players in the two-phase termination model are the following. Its class is shown in Figure 1.
Figure 1. Class diagram of two-phase termination mode
- Threadowner: The owner of the target thread. In the Java language, there is no concept of the owner of a thread, but the thread behind it is the task it is dealing with or the service it provides, so we can't just stop it without knowing what the thread is doing. In general, we can treat the creator of the target thread as the owner of the thread and assume that it "knows" the target thread's work and can safely stop the target thread.
- terminatablethread: A thread that can be stopped. Its main methods and responsibilities are as follows:
- Terminate: Sets the thread stop flag and sends a stop "signal" to the target thread.
- doterminate : leave the child class with some extra action required to implement a thread stop, such as socket I/O in the target thread code, the subclass can close the socket in the method to reach the fast stop thread, without causing the target thread to wait for I/O completion to detect the thread stop token.
- Dorun : leaves the child class with the processing logic to implement the thread. Equivalent to Thread.run, except that the method does not need to be concerned with the logic of stopping the thread, because this logic is already encapsulated in the Terminatablethread run method.
- Docleanup : leave the subclass with some cleanup actions that may be required after the thread has been stopped.
- Terminationtoken : thread stop flag. The toshutdown is used to indicate that the target thread can be stopped. Reservations can be used to reflect how many unfinished tasks the target thread still has, to support the end of the line until the target thread has finished processing its tasks.
The preparation phase of the sequence is shown in Figure 2:
Figure 2. Sequence diagram of the preparation phase
1, the client code calls the thread owner's shutdown method.
2. The shutdown method invokes the Terminate method of the target thread.
The-Terminate method sets the Terminationtoken Toshutdown flag to True.
5. The Terminate method calls the Doterminate method implemented by the Terminatablethread subclass so that the subclass can do some other necessary actions to stop the target thread.
6. If the reservations attribute value of Terminationtoken is 0, the target thread does not have an unhandled task or Threadowner does not care if it has an unhandled task while stopping the thread. At this point, the Terminate method invokes the interrupt method of the target thread.
7, Terminate method call end.
8. The shutdown call returns, at which point the target thread may still be running.
The execution phase is performed by the code of the target thread to examine the value of the Terminationtoken Toshutdown property, the reservations property, and to catch the associated exception thrown by the interrupt method call to determine whether to stop the thread. The Docleanup method implemented by the Terminatablethread subclass before the thread stop is called.
Two-phase Termination model actual case
A system needs a docking alarm system to realize the alarm function. Alarm system is a C/s structure system, which provides a set of client API (Alarmagent) for its docking system to send alarm. The system encapsulates the alarm function in a single-piece class called Alarmmgr (Singleton), where the other code in the system needs to send an alarm only to call the Sendalarm method of the class. This method caches the alarm information into the queue, which is responsible for calling the Alarmagent method to send the alarm information to the alarm server by the special alarm sending thread.
The alert send thread is a user thread, so the line Chengjo does not stop while the system is stopped, which prevents the JVM from shutting down gracefully. So, we have to take the initiative to stop the alarm sending thread while the system is stopping, rather than relying on the JVM. In order to be able to gracefully stop the alert sending thread as soon as possible, we need to address the following two questions:
- When the alarm cache queue is not empty, it is necessary to send the alarm information in the queue to the alarm server.
- Because the queue that caches the alert information is a blocking queue (Linkedblockingqueue), the alert send thread is waiting until the team is listed as empty. This causes it to fail to respond to our request to shut down the thread.
The above problems can be solved by using the two-phase termination mode.
The alarmmgr corresponds to the Threadowner participant instance in Figure 1, which is the owner of the alert send thread. The system stops by calling its shutdown method (Alarmmgr.getinstance (). Shutdown ()) to request that the alarm send thread stop. The code is shown in Listing 1:
Listing 1. Alarmmgr Source
public class Alarmmgr {private final blockingqueue<alarminfo> alarms = new Linkedblockingqueue<alarminfo> () ;//Alarm system client Apiprivate final alarmagent alarmagent = new Alarmagent ();//Alarm send thread private final abstractterminatablethread Alarmsendingthread;private Boolean shutdownrequested = false;private static final alarmmgr INSTANCE = new Alarmmgr ();p Riv Ate Alarmmgr () {alarmsendingthread = new Abstractterminatablethread () {@Overrideprotected void Dorun () throws Exception { if (alarmagent.waituntilconnected ()) {Alarminfo alarm;alarm = Alarms.take (); TerminationToken.reservations.decrementAndGet (); try {alarmagent.sendalarm (alarm);} catch (Exception e) { E.printstacktrace ();}}} @Overrideprotected void Docleanup (Exception exp) {if (null! = exp) {exp.printstacktrace ();} Alarmagent.disconnect ();}}; Alarmagent.init ();} public static Alarmmgr getinstance () {return INSTANCE;} public void Sendalarm (Alarmtype type, string ID, string extraInfo) {final Terminationtoken Terminationtoken = alarmsending ThreaD.terminationtoken;if (Terminationtoken.istoshutdown ()) {//Log the ALARMSYSTEM.ERR.PRINTLN ("Rejected alarm:" + ID + "," + extraInfo); return;} try {alarminfo alarm = new Alarminfo (ID, type); Alarm.setextrainfo (ExtraInfo); TerminationToken.reservations.incrementAndGet (); Alarms.add (alarm);} catch (Throwable t) {t.printstacktrace ();}} public void init () {Alarmsendingthread.start ();} Public synchronized void shutdown () {if (shutdownrequested) {throw new IllegalStateException ("Shutdown already requested !");} Alarmsendingthread.terminate (); shutdownrequested = true;} public int pendingalarms () {return alarmSendingThread.terminationToken.reservations.get ();}} Class Alarmagent {//Omit other code private volatile Boolean connectedtoserver = False;public void Sendalarm (alarminfo alarm) throw s Exception {//Omit other code System.out.println ("sending" + alarm); try {thread.sleep (50);} catch (Exception e) {}}public void init () {//Omit other code connectedtoserver = TRUE;} public void Disconnect () {//Omit other code System.out.println ("DisconneCTED from alarm server. "); public Boolean waituntilconnected () {//Omit other code return connectedtoserver;}}
As you can see from the code above, Alarmmgr adds a reservations value of Terminationtoken by 1 for each alarm message that is placed in the cache queue. While the alarm sending thread sends an alarm to the alarm server, it reduces the Terminationtoken reservations value by 1. This provides clues that we can ensure that the existing alarm information in the queue is processed before stopping the alarm sending thread: The Abstractterminatablethread Run method will determine if the thread to be stopped is no longer in accordance with the Terminationtoken reservations Or do not need to be concerned about whether or not it is a task to be addressed.
Abstractterminatablethread source code is shown in Listing 2:
Listing 2. Abstractterminatablethread Source
Public abstract class Abstractterminatablethread extends Thread implements terminatable {public final Terminationtoke n terminationtoken;public abstractterminatablethread () {super (); this.terminationtoken = new Terminationtoken ();} /** * * @param thread termination Flag instance shared between Terminationtoken threads */public abstractterminatablethread (Terminationtoken terminationtoken) { Super (); this.terminationtoken = Terminationtoken;} protected abstract void Dorun () throws exception;protected void Docleanup (Exception cause) {}protected void Doterminiate ( {} @Overridepublic void Run () {Exception ex = Null;try {while (true) {/* * * * * * * * * * * * * * * * processing logic on thread execution The flag that determines the thread to stop before it is started. */if (Terminationtoken.istoshutdown () && terminationToken.reservations.get () <= 0) {break;} Dorun ();}} catch (Exception e) {//allow the thread to terminate in response of a interrupt Invocationex = e;} finally {Docleanup (ex) ;}} @Overridepublic void Interrupt () {terminate ();} @Overridepublic void Terminate () {TerminationtoKen.settoshutdown (true); try {doterminiate ();} finally {//if there is no pending task, attempt to force termination of thread if (TerminationToken.reservations.get () <= 0) {super.interrupt ();}}}}
Abstractterminatablethread is a reusable Terminatablethread participant instance. Its Terminate method completes the preparation phase of the thread stop. The method first sets the Toshutdown variable of Terminationtoken to True, indicating that the target thread can be ready to stop. However, the target thread may be in a call to a blocking (Blocking) method, such as calling Object.sleep, Inputstream.read, and so on, and cannot detect the variable. Calling the interrupt method of the target thread causes some blocking methods (see table 1) to stop the target thread by throwing an exception. However, some blocking methods, such as Inputstream.read, do not respond to interrupt method calls, and the Doterminiate method needs to be implemented by subclasses of Terminatablethread, where additional operations are required to close the target thread. For example, in socket synchronous I/O by closing the socket, the thread that is using the socket will throw socketexception if it is in I/O waiting. Therefore, the Terminate method next calls the Doterminate method. Then, if the value of terminationtoken.reservations is non-positive (indicating that the target thread has no pending tasks, or if we are not concerned about whether it needs to be processed), then the Terminate method invokes the interrupt method of the target thread. Forces the target thread's blocking method to break, forcing the target thread to terminate.
The execution phase is completed in the Abstractterminatablethread run method. The method terminates the thread by judging the Toshutdown property of the Terminationtoken and the reservations property, or by capturing the exception thrown by the interrupt method call. and call the Docleanup method implemented by the Terminatablethread subclass to perform some cleanup actions before the thread terminates.
During the implementation phase, Since the Abstractterminatablethread.run method performs the threading logic (implemented by calling the Dorun method) each time, the value of the Toshutdown property and the reservations property are evaluated before the target thread finishes processing its pending task (reserva The Tions property has a non-positive value) and the target thread's Run method exits the while loop. Therefore, the threading logic code (the Dorun method) of the thread will no longer be called, allowing this case to be resolved without using the two-phase termination mode to stop two problems with the target thread (the target thread can be guaranteed to finish processing the pending task before it is stopped) Sending the existing alarm information in the queue to the server) and evasion (the Dorun method is no longer called after the target line Cheng sends out the existing alarm in the queue, thus avoiding the blocking caused by the Blockingqueue.take call when the queue is empty).
From the above, the preparation stage and the execution stage need to coordinate the action of both by Terminationtoken as "intermediary". The source code for Terminationtoken is shown in Listing 3:
Listing 3. Terminationtoken Source
public class Terminationtoken { //uses the volatile modifier to ensure that the variable's memory visibility is protected volatile Boolean Toshutdown = False without displaying a lock; Public final Atomicinteger reservations = new Atomicinteger (0);p ublic boolean istoshutdown () {return toshutdown;} protected void Settoshutdown (Boolean toshutdown) {This.toshutdown = true;}}
Evaluation and implementation of two-phase termination model
The two-phase termination mode allows us to gracefully stop the various forms of target threads. If the target thread invokes a blocking method that responds to the interrupt method call, the target thread calls a blocking method that cannot respond to the interrupt method call, and the target thread acts as a consumer for the production of other threads, the product is processed before it stops, and so on. Two-phase termination mode implements a thread stop that may be delayed, that is, after the client code has called the Threadowner.shutdown, the thread may still be running.
This example shows a reusable two-phase termination pattern implementation code. For readers to implement the pattern themselves, there are several issues that may require attention.
Thread Stop Flag
This case uses Terminationtoken as a flag that the target thread can prepare to stop. From the code in Listing 3, we can see that Terminationtoken uses the Boolean variable Toshutdown as the primary stop flag, rather than using thread.isinterrupted (). This is because the interrupt method that invokes the target thread does not guarantee that the target thread's isinterrupted () method returns a value of true: the target thread might call some code that captures interruptedexception without leaving the thread interrupt state. In addition, toshutdown this variable to ensure memory visibility and avoids the overhead of using explicit locks, using volatile modifiers. This is also important, I have seen some of the use of the Boolean variable as the thread stop flag code, but these variables are not modified with volatile, and access to it is not locked, which may not stop the target thread.
Producer--a thread in consumer issues stops
In multithreaded programming, many problems and some multithreaded programming patterns can be regarded as producer-consumer problems. Stopping the thread in the producer-consumer issue requires more consideration: Note the thread's stop order, and if the consumer thread stops first than the producer thread, the new "product" produced by the producer cannot be processed, If you stop the producer thread first, you may leave the consumer thread in an empty wait (such as a producer consumer using a blocking queue to relay "products"). Also, it is an issue to consider whether to wait until the consumer thread has finished processing all pending tasks or to make a backup of those tasks. The case of this article shows the processing of the middle thread stop of the producer-consumer problem, whose core is to use the reservations variable of Terminationtoken: The producer produces one product per production, two-phase The caller code of the termination mode increases the value of the reservations variable by 1 (TerminationToken.reservations.incrementAndGet ()), and the consumer thread processes one product per Two-phase the caller code of the termination pattern to reduce the value of the reservations variable by 1 (TerminationToken.reservations.decrementAndGet ()). Of course, if we do not care about the task to be processed when we stop the consumer thread, the caller code of two-phase termination mode can ignore the operation of the reservations variable. Listing 4 shows an example of a complete stop producer-a thread in consumer issues:
Listing 4. Stop producers--examples of threads in consumer issues
public class Producerconsumerstop {class Sampleconsumer<p> {private final blockingqueue<p> queue = new Linked Blockingqueue<p> ();p rivate abstractterminatablethread workthread = new Abstractterminatablethread () {@ overrideprotected void Dorun () throws Exception {TerminationToken.reservations.decrementAndGet (); P Product = Queue.take ();//... SYSTEM.OUT.PRINTLN (product);}}; public void Placeproduct (P product) {if (WorkThread.terminationToken.isToShutdown ()) {throw new IllegalStateException ( "Thread shutdown");} try {queue.put (product); WorkThread.terminationToken.reservations.incrementAndGet ();} catch (Interruptedexception E {}}public void shutdown () {workthread.terminate ();} public void Start () {Workthread.start ();}} public void Test () {final sampleconsumer<string> Aconsumer = new sampleconsumer<string> (); Abstractterminatablethread aproducer = new Abstractterminatablethread () {private int i = 0; @Overrideprotected void Dorun ( ) throws Exception {Aconsumer.placeproduct(String.valueof (i));} @Overrideprotected void Docleanup (Exception cause) {////producer thread stopped before requesting to stop the consumer thread Aconsumer.shutdown ();}; Aproducer.start (); Aconsumer.start ();}}
Hide and not expose a thread that can be stopped
In order to ensure that a thread that can be stopped is not stopped by other code, we will normally stop the thread from hiding behind the threads owner, leaving the other code in the system unable to access the thread directly. As shown in this case code (see Listing 1): Alarmmgr defines a private field alarmsendingthread used to refer to the alert send thread (a thread that can be stopped), Other code in the system can only request that the thread stop by calling Alarmmgr's shutdown method, rather than stopping it by referencing the thread object itself.
Summarize
This paper introduces the intention and architecture of two-phase termination model. Combined with the author's work experience provides a practical case to show a reusable two-phase termination pattern implementation code, on the basis of this model is evaluated and shared in the actual use of the pattern needs to be noted.
Reference Resources
- Source code for this article read online: https://github.com/Viscent/JavaConcurrencyPattern/
- Brian Göetz et al.,java Concurrency in practice
- Mark Grand,patterns in Java,volume 1, 2nd Edition
Two-phase termination mode