The basics of Threads on the JVM are fundamental. There is a class in Java called Thread. Constructing a Thread creates a new OS thread. You pass it a function when constructing it. Then you can start it with the
We also look at how to cleanly exit the JVM and how to hook into the shutdown procedure. This lets us run code to clean up before the JVM exits.
Code is available: lispcast/number-cruncher
Code for this particular lesson is available at the
You can checkout the code in your local repo with this command:
$CMD git clone firstname.lastname@example.org:lispcast/number-cruncher.git $CMD cd number-cruncher $CMD git checkout -f threads
We are now going to take a look, a quick look at Java Threads, JVM threads. So the Java Virtual Machine has a special class called Thread that actually represents a thread that gets run in the JVM. So it's a class, but it's handled a special way. It has a meaning besides just its methods and its state. It also represents a physical thread in the operating system. So there's not that much to know about them.
The first thing is how to construct them. So here I have this main class, or this main function. And if I run it, it's just gonna print running. Which I can do. So how do you create a thread? See, and then it exits. That's great. How do you create a thread? It's actually easy, you just construct it. Thread dot. And you can pass it in something called a runnable, but luckily functions are runnable. So I just gave it a function.
It takes no arguments, and now I can do stuff. So let's say I just want to loop forever, printing something. So I'm gonna do loop [x,0] I'm gonna sleep for one second, then I'm gonna print x, and then I'm gonna recur with inc x. Okay, so this is going to create the thread, but it doesn't start it. Okay, so I have to do .start on it. Okay, so you can have the thread kinda waiting. You haven't started it yet, but once you call start it's gonna run, and there is no way to shut it down, except shutting down the whole JVM. Or from the inside it can stop recurring somehow if it knew, it could stop itself.
So let's run this. And we see it's printing out the numbers. I can hit ctrl+c and it'll kill it, but that would run forever. Now the thing is, I can shut down the whole JVM. This is something that is important to know. If you call, Okay, let's back up. If I call Thread/sleep from here, this is gonna sleep the main thread because it's not in this thread. It's gonna sleep the main thread. Let's say I wanna sleep for 10 seconds and then exit. Exit is gonna stop all threads, do a clean up, and then exit the JVM. And this 0, is because in Unix the exit status 0 means it's successful. Alright, so this is actually going to stop the thread after 10 seconds, it's gonna stop everything after 10 seconds.
If you don't call exit, this function will end. The thread sleep will happen. Like if I get rid of the exit, the function will finish, but this thread is still running, and so the JVM continues to run. But with the exit it'll clean up and get everything back to your shell. So I'll run this. Should run for 10 seconds, and then exit. Okay, there we go. Didn't count quite all the way up there cuz the timing is not so precise. But it did print out nine numbers, instead of 10. That's fine.
So there's a few other things with start up and shut down that are actually interesting to know. Like let's say this thread throws an exception. What's going to happen if this thing throws? I'm gonna do throw an exception. Let's see what happens when I do that. Because it's not the main thread, it's a different thread. Okay, notice it hasn't exited yet. Boom, now it exited because that main thread called system exit. Alright, but we might want to handle that. You could put a try catch obviously. That would handle it just fine, but you could also have something that is called a default uncaught exception handler.
The uncaught sorry, not the default, the uncaught exception. So I'm gonna remove this start, and thread is like a lot of Java classes, it is stateful. You have to call all these methods on it, set stuff up, all these configuration options, and then you call start. And there's a nice built-in function called Doto. Or it's a macro called Doto that helps you do that kind of stuff. So this is the same as what I had before. So it's gonna construct the thread, and then call start. But before I call start on it I want to do Sorry, just referring to my notes. Alright, now the thing is it's going to have to, It's gonna take an uncaught exception handler which is its own class, called thread.UncaughtExceptionHandler.
Okay, so we're gonna have to reify that. Reify thread. And now we need the dollar sign. UncaughtExceptionHandler, and it has one method called uncaughtException. Okay it's gonna take a this. It's gonna take the thread, and it's gonna take the exception. It's actually a throwable, it could be other stuff besides that, and I'm just going to print the exception.
Okay, let's see if this is gonna work. There we go. Notice it had a different behavior. It did a print instead of this other stack trace. So you can do other stuff in there. Notice the exception still got thrown. It still broke out of the loop, but you might wanna do some clean up or something. You could say Okay another thing, a thread cannot be restarted. You can't take this thread, start, let it run to completion, and then just call start on it again. You need to make a new one.
So, you can use the same function, but you have to construct a new thread. One last thing on here is that, you can actually call some code when it's time to shut down the JVM. And this one is a little bit tricky because you don't wanna run a bunch of stuff. You don't wanna take a lot of time, because everything's shutting down and it's time to get out of there. And sometimes the operating system, if you're powering down your computer, you say, just power down and it's closing all the programs you've got open. And it's like, "Oh this one is not responding."
That's what's happening is it's got this signal that it's time to exit, and it's taking too long. So you wanna do this really quickly, but you know, sometimes it's what you wanna do. The other thing is that you don't know what threads it's still running, because they're all getting shut down. So you can't rely on services being there. That other threads are handling. Keeping that in mind, let's look at how to do it. So, the first thing is this is a method on runtime. Makes sense right? The runtime is a class that represents the running JVM. And you can do an addShutdownHook, and this is gonna get called whenever, we should actually do it before the thread. Let me move it back up here. It's probably best to do it first.
The first thing you do is add a shut down hook, because if anything throws an exception or anything before this is set up, it's not gonna get run. And also you can't set this up after the shutdown is happening. Once that happens, it's too late. So you need to set it up really fast. So it's a method on the runtime, and here's the thing: it takes a thread. So it takes an un-started thread, and it's gonna run this when everything is shutting down. So you construct it in the same way as before. And we can put something like this, There.
So that's gonna add the shut down hook. Notice we're not printing anything when we exit. We're printing it up here at the shut down. So we could print shutting down right before we call exit, but I wanna see it run here. Okay, let's go, we'll see this run. So it should take 10 seconds to run. That cleaning up is the exception handler that we had on the thread, and then boom, shutting down got run when we called system exit. And actually, you can get rid of the system exit.
This is a thing, if you don't do the system exit, you might have other threads running that you didn't know about. Some library could have started a thread. And so you end the main function and you're like, "Why didn't it shut down?" Something, some thread pool somewhere might still be processing stuff. It's a good idea to do the system exit, but I'm gonna get rid of it, just to see what happens. There. In this case there wasn't any other thread running, so it did exit. But something you could have Core A synced and it's got threads running somewhere, you could have been using agents and they're still going.
There's all sorts of possibilities there. Maybe you have some http requests that's still in flight. System exit is what you wanna do at the end of your main. I mean, unless you want it to run forever. That's a different question. So there you go. This is how you cleanly shut down, and this is how you make sure that you can run some code before it shuts down, but when it's time to shut down and this is how you make a thread and set its exception handler.