Thoughts caused by a Java thread started by a scheduled executorservice hanging up for no reason

December 12, 2018 18:44:53

Thoughts caused by a Java thread started by a scheduled executorservice hanging up for no reason

Case scene

Not long ago, when developing and transforming an end-to-end monitoring log system of the company, there was a bug: a thread scanning tables and writing logs died for no reason.

track down sb. by following clues

I looked at the code for a long time and didn't think of any logic problems. The best way is to restart. When I happily thought that restarting was a good way, the problem reappeared.

I looked at my code a little reluctantly

ID: perfect logic. Is there anything else I didn't notice?

True self: of course, you rookie, there are many places you don't know.

So I went to the boss and asked him how to solve the problem. The boss said to import 100000 data from the production database to the test library, and then debug locally. Then, I export 10000 data from the database to start the test, start the process in eclipse, and write the log in the local file. Soon, the problem appeared again. Then the breakpoint, and then find the problem. The problems are as follows:

One line of code:

String timeStamp = DateUtil.str2Date(receiveTime, DateUtil.YYYYMMDDHH24MISS).getTime() + "000";

This line of code means that the receiving time of the string is formatted as receiveTime, and getTime() gets the timestamp, because the format is microseconds, plus three zeros.

DateUtil.str2Date method: (String time is converted to Date type. For time conversion, please see your own Conversion of String, Date and Timestamp)

public static Date str2Date(String dateStr, String dateFormat){
	if (StringHelper.isEmpty(dateStr)) {
		return null;
	}

	SimpleDateFormat df = new SimpleDateFormat(dateFormat);
	try {
		return df.parse(dateStr);
	} catch (Exception ex) {
		return null;
	}
}

This tool class returns null when the parse method throws an exception. It seems that there is no problem, but I don't judge whether it is null after the conversion. Then it becomes null.getTime(), and then throws a very common NullPointerException exception.

Here, it seems that the problem has been solved, but the problem is not so simple.

get to the root of things

As mentioned above, a NullPointerException exception is thrown in the thread. The solution is to add a condition to judge whether it is empty. However, generally speaking, when there is an exception, the program does not catch the exception. The console will print the exception information in the log or during debug ging, similar to this:

at com.netease.backend.rds.task.CleanHandleThread.run(CleanHandleThread.java:65)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:204)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:662)

But in fact, when I debug ged, I didn't see the printed exception information. When I reached the breakpoint and found that the next line of code didn't execute, I decided that the problem was here, and the null pointer exception could be seen at once. The problem came, why didn't I print the exception information? I think it should be a thread problem. Start the log writing timer in the code The service uses ScheduledExecutorService:

I googled and found that many predecessors had encountered this problem.

In these articles, I found the answer I wanted. I quoted one of them This paper discusses the basic principles of analyzing the location problem from an example of java thread hanging up Text as the answer.

Any throw exception or error realizing the executor causes the executor (scheduled executorservice) to halt. No more invocations on the runnable, no more work done. This work stoppage happens silently, you'll not be informed

That is, if the user throws an exception, the scheduled executorservice will stop the thread without any error and prompt.

This is why the print exception information is not seen in the log or the console.

resolvent

I wrote a test class. If you are interested, you can study this bug.

public class ScheduledExecutorServiceThrowExceptionTest {
	
	private static int i = 0;
	
	public static void main(String[] args) {
		ScheduledExecutorService exc = Executors.newSingleThreadScheduledExecutor();
		exc.scheduleAtFixedRate(new Runnable(){

			@Override
			public void run() {
				i++;
				if (i==6) {
					throw new RuntimeException();
				} else {
					System.out.println(i);
				}
				
			}
			
		}, 0, 1, TimeUnit.SECONDS);
			
	}

}

The test results are:

The results show that when the program throws an exception, the thread no longer runs, that is, it hangs.

resolvent:

  • 1. Directly add a try catch to catch exceptions, and then you can print the exception information you need or handle exceptions.
public class ScheduledExecutorServiceThrowExceptionTest1 {
	
	private static int i = 0;
	
	public static void main(String[] args) {
		ScheduledExecutorService exc = Executors.newSingleThreadScheduledExecutor();
		exc.scheduleAtFixedRate(new Runnable(){

			@Override
			public void run() {
				try {
					// doSomething();// TODO: specific business logic
					i++;
					if (i==6) {
						throw new RuntimeException();
					} else {
						System.out.print(i + " ");
					}
				} catch (Exception ex) {
					System.out.println();
					System.out.println("stay ScheduledExecutorService Exception thrown in, exception stack:" + ex.getStackTrace());
				}
			}
			
		}, 0, 1, TimeUnit.SECONDS);
			
	}

}

The result is that the exception information is printed and the thread is not interrupted.

1 2 3 4 5 
stay ScheduledExecutorService Exception thrown in, exception stack:[Ljava.lang.StackTraceElement;@1bb53ed8
7 8 9 10 11 12 13 
  • 2. Return exception information through the ScheduledFuture object
public class ScheduledExecutorServiceThrowExceptionTest2 {

	private static int i = 0;
	
	public static void main(String[] args) {
		ScheduledExecutorService exc = Executors.newSingleThreadScheduledExecutor();
		ScheduledFuture<?> handle = exc.scheduleAtFixedRate(new Runnable(){

			@Override
			public void run() {
				i++;
				if (i==6) {
					throw new RuntimeException();
				} else {
					System.out.print(i + " ");
				}
				
			}
			
		}, 0, 1, TimeUnit.SECONDS);
		
		try {
			handle.get();
		} catch(Exception ex) {
			System.out.println();
			System.out.println("stay ScheduledExecutorService Exception thrown in, exception stack:" + ex.getStackTrace());
		}
			
	}

}

This solution prints the exception message, but does not prevent the thread from hanging.

1 2 3 4 5 
stay ScheduledExecutorService Exception thrown in, exception stack:[Ljava.lang.StackTraceElement;@33909752

summary

The reason why a Java thread started by a ScheduledExecutorService hangs up for no reason is that if the user throws an exception, the ScheduledExecutorService will stop the thread without an error and no prompt. The solution is to try catch to print the exception information, or use scheduledfuture <? > to obtain the thread running results.

If you write more bug s, you will have more natural experience, but you should pay attention to summary.

finish

09:08:19, December 13, 2018

Posted on Mon, 29 Nov 2021 07:09:21 -0500 by Gecko24