How to Make Thread Sleep Until Focused Again
JVM Languages like Coffee and Scala take the ability to run concurrent code using the Thread
form. Threads are notoriously complex and very error prone, so having a solid agreement of how they work is essential.
Allow'south start with the Javadoc for Thread.sleep
:
Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds
What are the implications of terminate execution
, also known as blocking, and what does it mean? Is information technology bad? And if and then can we achieve not-blocking sleep?
What We'll Encompass in This Article
This mail covers a lot of basis and hopefully you will learn a lot of absurd things.
- What happens at the Bone level when sleeping?
- The problem with sleeping
- Project Loom and virtual threads
- Functional programming and blueprint
- ZIO Scala library for concurrency
Yes, all of this is coming upwardly below.
But kickoff, allow's outset with this simple Scala snippet that nosotros will change throughout the postal service to achieve what we want:
println("a") Thread.sleep(1000) println("b")
It's quite uncomplicated: it prints "a" and then 10 seconds later in prints "b"
Let's focus on Thread.sleep
and endeavour to sympathise HOW it achieves sleeping. In one case we understand the how, we volition be able to see the trouble and ascertain it more than concretely.
How Does Sleeping Work at the OS Level?
Here is what happens when you call Thread.sleep
under the hood.
- It calls the thread API of the underlying Os
- Because the JVM uses a ane to ane mapping between Java and kernel threads, it asks the OS to give upward the thread's "rights" to the CPU for the specified time
- When the time has elapsed the OS scheduler will wake the thread via an interrupt (this is efficient) and assign it a CPU slice to permit it to resume running
The critical point here is that the sleeping thread is completely taken out and is not reusable while sleeping.
Limitations of Threads
Here are few important limitations that come with threads:
- At that place is a limit to how many threads you can create. After around 30K, you will get this mistake:
java.lang.OutOfMemoryError : unable to create new native Thread
- JVM Threads can be expensive memory-wise to create, equally they come up with a dedicated stack
- As well many JVM threads will incur overhead because of expensive context switches and the way they share finite hardware resources
At present that we understand more than nearly what goes on backside the scenes permit's become back to the sleeping problem.
The Problem with Sleeping
Let's define the trouble more concretely and run a snippet to show the issue nosotros are facing. We will utilize this office to illustrate the point:
def task(id: Int): Runnable = () => { println(s"${Thread.currentThread().getName()} start-$id") Thread.sleep(10000) println(due south"${Thread.currentThread().getName()} end-$id") }
This simple function will
- print
kickoff
followed by the thread id - sleeps for 10 seconds
- print
cease
followed by the thread id
Your mission if yous accept it is to run 2 tasks concurrently with one thread
We desire to run ii tasks concurrently, significant the whole program should take a total of x seconds. But we only have ane thread available.
Are y'all up for this claiming?
Let'southward play a niggling flake with the number of tasks and threads to become a sense of what the problem is exactly.
one task -> ane thread
new Thread(task(1)).get-go()
12:11:08 INFO Thread-0 first-1 12:11:18 INFO Thread-0 end-ane
Allow's burn up jvisualvm
to bank check out what the thread is doing:
You can see that the Thread-0 is in the purple sleeping
state.
Striking the thread dump button volition print this:
"Thread-0" #13 prio=5 os_prio=31 tid=0x00007f9a3e0e2000 nid=0x5b03 waiting on condition [0x0000700004ac8000] coffee.lang.Thread.Country: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at case.Weblog$.$anonfun$task$1(Weblog.scala:seven) at example.Web log$$$Lambda$two/1359484306.run(Unknown Source) at coffee.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - None
Clearly, this thread is not usable anymore until it finishes to sleep.
2 tasks -> 1 thread
Let's illustrate the problem by running 2 such tasks with only one thread available:
import coffee.util.concurrent.Executors // an executor with merely one thread available val oneThreadExecutor = Executors.newFixedThreadPool(1) // send ii tasks to the executor (one to two).foreach(id => oneThreadExecutor.execute(job(id)))
We get this output:
2020.09.28 21:49:56 INFO puddle-1-thread-1 start-one 2020.09.28 21:50:07 INFO pool-i-thread-i end-ane 2020.09.28 21:l:07 INFO puddle-1-thread-one start-ii 2020.09.28 21:50:17 INFO pool-1-thread-ane end-2
Yous tin can see the purple color (sleeping state) for the pool-ane-thread-ane
. The tasks have no choice just to run one later the other because the thread is taken out each time Thread.sleep
is used.
2 tasks -> ii threads
Allow'due south run the aforementioned code with 2 threads bachelor. We get this:
// an executor with 2 threads bachelor val oneThreadExecutor = Executors.newFixedThreadPool(ii) // send 2 tasks to the executor (i to 2).foreach(id => oneThreadExecutor.execute(task(id)))
2020.09.28 22:42:04 INFO pool-1-thread-two start-2 2020.09.28 22:42:04 INFO pool-ane-thread-1 outset-1 2020.09.28 22:42:xiv INFO puddle-1-thread-one end-i 2020.09.28 22:42:14 INFO puddle-one-thread-2 end-ii
Each thread can run 1 task at a time. We finally accomplished what we wanted, running 2 tasks concurrently, and the whole program finished in 10 seconds.
That was easy considering nosotros used ii threads (puddle-1-thread-1 and pool-one-thread-2), just we desire to do the same with simply 1 thread.
Let's identify the problem and and so observe a solution.
The problem : Thread.sleep is blocking
We at present understand that we cannot use Thread.sleep
– it blocks the thread.
This makes information technology unusable until information technology resumes, preventing us from running 2 tasks meantime.
Fortunately, there are solutions, which nosotros'll hash out adjacent.
First Solution: Upgrade your JVM with Project Loom
I mentioned before that JVM threads map 1 to one to OS threads. And this fatal pattern error leads us hither.
Project Loom aims to correct that past adding virtual threads.
Here is our code rewritten using virtual threads from Loom:
Thread.startVirtualThread(() -> { System.out.println("a") Thread.sleep(1000) System.out.println("b") });
The amazing thing is that the Thread.slumber
volition not block anymore! It's fully async. And on peak of that, virtual threads are super inexpensive. Yous could create hundreds of thousands of them without overhead or limitations.
All our issues are solved at present – well besides the fact that Projection Loom will non be available until at to the lowest degree JDK 17 (equally of at present scheduled for September 2021).
Oh well, let'due south get back and try to solve the sleeping problem with what the JVM currently gives us.
Key insight: You can express sleeping in terms of scheduling a job in the future
If you tell your boss that you lot are busy and you will resume your work in 10 minutes, your boss does not know that you are about to take a nap. They only run into that you started your piece of work in the morning time so paused for 10 minutes then resumed.
This:
first sleep(10) end
is equivalent from the outside to this:
first resumeIn(10s, end)
What we did above is to SCHEDULE the chore to end in 10 seconds.
That's information technology, nosotros don't demand to sleep anymore. We just need to be able to schedule things in the futurity instead.
We've reduced i problem with another, ane that is easier and has a simpler solution.
The scheduling problem
Luckily for us, scheduling tasks is very simple to do. We just have to switch out the executor as follows:
val oneThreadScheduleExecutor = Executors.newScheduledThreadPool(1)
We can now use the schedule
function instead of execute
:
oneThreadScheduleExecutor.schedule (chore(1),10, TimeUnit.SECONDS)
Well that's not exactly what nosotros want. We want to split the offset and end printing by ten seconds, and so let's modify our chore function as follows:
def nonBlockingTask(id: Int): Runnable = () => { println(s"${Thread.currentThread().getName()} start-$id") val endTask: Runnable = () => { println(south"${Thread.currentThread().getName()} end-$id") } //instead of Thread.sleep for 10s, we schedule it in the future, no more blocking! oneThreadScheduleExecutor.schedule(endTask, 10, TimeUnit.SECONDS) }
2020.09.28 23:35:45 INFO pool-1-thread-1 start-1 2020.09.28 23:35:45 INFO pool-i-thread-1 outset-2 2020.09.28 23:35:56 INFO pool-1-thread-1 end-1 2020.09.28 23:35:56 INFO pool-1-thread-1 end-two
Yeah! We did it! Only i thread and 2 concurrent tasks that "sleep" 10 seconds each.
Ok great, but yous cannot really write code like this. What if you want some other task in the eye every bit follows:
00:00:00 start 00:00:ten middle 00:00:20 end
Y'all would demand to modify the implementation of the nonBlockingTask
and add another call to schedule
in at that place. And that volition get pretty messy very quickly.
How to Use Functional Programming to Write a DSL with a Non-Blocking Sleep
Functional Programming in Scala is a joy, and writing a DSL (domain-specific language) using FP principles is quite easy.
Permit'southward offset at the end. We would like our concluding plan to wait something like this:
def nonBlockingFunctionalTask(id: Int) = { Print(id,"start") andThen Impress(id,"center").slumber(1000) andThen Print(id,"end").slumber(1000) }
This mini-linguistic communication will reach exactly the same behavior equally our previous solution but without exposing all the nasty internals of the scheduled executor and threads.
The model
Let'southward ascertain our data types:
object Task { sealed trait Task { cocky => def andThen(other: Task) = AndThen(self,other) def slumber(millis: Long) = Sleep(self,millis) } example class AndThen(t1: Task, t2: Task) extends Task case grade Print(id: Int, value: Cord) extends Task case course Slumber(t1: Job, millis: Long) extends Task
In FP the data types just hold data and no behavior. So this whole code does "cypher" – it just captures the language structure and information we want.
We need ii functions:
-
slumber
to brand a task sleep -
andThen
to chain tasks
Discover that their implementation does zippo. Information technology merely wraps information technology in the correct class and that's it.
Permit'southward use our nonBlockingFunctionalTask
role:
import Task._ //create 2 tasks, this does not run them, no threads involved here (1 to 2).toList.map(nonBlockingFunctionalTask)
Information technology'southward a clarification of the problem. It does nothing, it but builds a list with 2 tasks, each one describing what to do.
If we print the result in the REPL we become this:
res3: Listing[Task] = List( //first task AndThen(AndThen(Print(i,start),Sleep(Print(1,middle),10000)),Sleep(Print(1,terminate),10000)), //second task AndThen(AndThen(Print(2,start),Sleep(Print(2,middle),10000)),Sleep(Impress(2,finish),10000)) )
Permit's write the interpreter
that volition turn this tree into one that'southward actually running the tasks.
The interpreter
In FP the role that turns a description into an executable program is chosen the interpreter
. Information technology takes the description of the program, the model, and interprets information technology into an executable form. Here it will execute and schedule the tasks directly.
We kickoff need a Stack
that volition allow usa to encode the dependencies betwixt tasks. Think that start >>= middle >>= end
will each be pushed to the stack and and then popped in order of execution. This will be evident in the implementation.
And at present the interpreter (don't worry if you don't understand this lawmaking, it's a bit complicated, in that location is a simpler solution coming upward):
def interpret(chore: Task, executor: ScheduledExecutorService): Unit = { def loop(current: Chore, stack: Stack[Task]): Unit = current friction match { example AndThen(t1, t2) => loop(t1,stack.push button(t2)) example Print(id, value) => stack.popular match { case Some((t2, b)) => executor.execute(() => { println(south"${Thread.currentThread().getName()} $value-$id") }) loop(t2,b) case None => executor.execute(() => { println(s"${Thread.currentThread().getName()} $value-$id") }) case Slumber(t1,millis) => val r: Runnable = () =>{loop(t1,stack)} executor.schedule(r, millis, TimeUnit.MILLISECONDS) } loop(task,Nil) }
And the output is what we want:
2020.09.29 00:06:39 INFO pool-ane-thread-1 commencement-1 2020.09.29 00:06:39 INFO pool-1-thread-1 start-ii 2020.09.29 00:06:50 INFO pool-one-thread-i middle-i 2020.09.29 00:06:50 INFO puddle-one-thread-one centre-2 2020.09.29 00:07:00 INFO pool-1-thread-1 end-ane 2020.09.29 00:07:00 INFO pool-i-thread-1 stop-two
One thread running 2 concurrent sleeping tasks. That's a lot of code and a lot of work. As usual, you should always ask yourself whether in that location a library that already solves this problem. Turns out there is: ZIO.
Non-Blocking Sleep in ZIO
ZIO
is a functional library for asynchronous and concurrent programming. Information technology works in a like mode to our little DSL, because it gives you lot a few types yous tin can mix and match to draw your program and nothing more.
And and so information technology gives us an interpreter that lets you run a ZIO plan.
Every bit I said this interpreter pattern is pervasive in the world of FP. Once y'all get information technology, a new world opens up to you.
ZIO.slumber
– a better version of Thread.sleep
ZIO
gives us the ZIO.slumber
function, a non-blocking version of Thread.sleep
. Here is our function written using ZIO
:
import zio._ import zio.panel._ import zio.elapsing._ object ZIOApp extends zio.App { def zioTask(id: Int) = for { _ <- putStrLn(s"${Thread.currentThread().getName()} beginning-$id") _ <- ZIO.sleep(10.seconds) _ <- putStrLn(southward"${Thread.currentThread().getName()} end-$id") } yield ()
It'due south strikingly similar to the commencement snippet:
def task(id: Int): Runnable = () => { println(s"${Thread.currentThread().getName()} beginning-$id") Thread.sleep(10000) println(s"${Thread.currentThread().getName()} terminate-$id") }
The clear difference is the for
syntax that allows us to chain statements with the ZIO
type. It'southward very similar to the andThen
part from our previous mini-language.
Every bit before with our mini-language, this programme is just a description. It'southward pure data, and information technology does nothing. To do something nosotros need the interpreter.
The ZIO interpreter
To interpret a ZIO program, yous just have to extend the ZIO.App
interface and put it in the run
method and ZIO
volition take care of running information technology, similar this:
object ZIOApp extends zio.App { override def run(args: List[String]) = { ZIO //starting time 2 ZIO tasks in parallel .foreachPar((1 to two))(zioTasks) //complete program when done .every bit(ExitCode.success) }
And we get this output – the tasks complete correctly in ten seconds:
2020.09.29 00:45:12 INFO zio-default-async-iii-1594199808 outset-2 2020.09.29 00:45:12 INFO zio-default-async-2-1594199808 kickoff-1 2020.09.29 00:45:33 INFO zio-default-async-7-1594199808 terminate-1 2020.09.29 00:45:33 INFO zio-default-async-8-1594199808 end-2
Takeaways
- Each JVM Thread maps to an OS thread, in a one to one fashion. And this is the root of a lot of problems.
-
Thread.sleep
is bad! It blocks the electric current thread and renders it unusable for further work. - Project Loom (that volition be available in JDK 17) will solve a lot of issues. Here is a cool talk about information technology.
- You can use
ScheduledExecutorService
to accomplish non-blocking sleep. - Yous tin can utilise Functional Programming to model a language where doing sleep is non-blocking.
- The ZIO library provides a non-blocking sleep out of the box.
Acquire to code for free. freeCodeCamp's open up source curriculum has helped more twoscore,000 people get jobs every bit developers. Get started
Source: https://www.freecodecamp.org/news/non-blocking-thread-sleep-on-jvm/
0 Response to "How to Make Thread Sleep Until Focused Again"
Post a Comment