How to Make Thread Sleep Until Focused Again

How to Use Thread.sleep Without Blocking on the JVM

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:

vissualm_vm_1

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              
visualvm_3

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.

visulavm_4

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

kleinyoute1972.blogspot.com

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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel