Preemptive vs. Cooperative scheduling3 min read

In an operation system, multitasking refers to the ability to execute multiple tasks concurrently; there are primarily two different types of multitasking: preemptive and cooperative. These methods are used by the OS to manage and switch between tasks.

Simply put, in preemptive scheduling, the OS decides or forces when to pause the current working thread and allow other threads to run. For example, context switching is a crucial aspect of preemptive scheduling, in which the OS keeps switching between threads to let every thread have time to run even when the current thread has not yet done its execution. And, of course, before switching, the state of the current thread is saved by the OS so that later on, it can continue its work. In cooperative scheduling, threads voluntarily yield their execution or wait for a designated point to give control to other threads.

In Java, there is no safe way to preemptively stop a thread, meaning that you cannot forcefully stop the execution of a thread right at the point you want, but rather, thread scheduling is handled by the OS. Here is an example that employs preemptive scheduling:

public static void main(String[] args) {
  Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Thread 1 executing iteration: " + i);
    }
  });

  Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Thread 2 executing iteration: " + i);
    }
  });

  thread1.setPriority(Thread.MAX_PRIORITY); // Higher priority
  thread2.setPriority(Thread.MIN_PRIORITY); // Lower priority

  thread1.start();
  thread2.start();
}

In this example, if the multitasking type were preemptive, we expect that the JVM would interrupt the thread with a lower priority and give the execution time to the higher priority thread to finish its execution before the other. However, threads in Java run on top of native threads, and they depend on the scheduling policy at the OS level; with that being said, when we run the program, the output can vary, and the thread1 doesn’t always finish before the thread2.

For cooperative scheduling, threads voluntarily give up their execution and have their own cancellation policy, as demonstrated in the following example:

public static void main(String[] args) {
    Thread myThread = new Thread(() -> {
      for (int i = 0; i < 5; i++) {
        System.out.println("Thread running: " + i);
        try {
          Thread.sleep(1000); // simulating some task execution
        } catch (InterruptedException e) {
          System.out.println("Thread interrupted while sleeping.");
          Thread.currentThread().interrupt(); // re-set the interrupted flag
          return; // exit on interruption
        }
      }
      System.out.println("Thread finished its work.");
    });
   myThread.setName("myThread");
   myThread.start();
   try {
      Thread.sleep(2000);
      myThread.interrupt();
   } catch (InterruptedException e) {
      e.printStackTrace();
   }
 }

Here, we apply the cooperative scheduling mechanism by using the Thread#interrupt() method, in the code fragment, we start the myThread from the main thread and make the main thread sleep for 2 seconds, after that, we interrupt the thread that we’ve created. Inside the myThread, each loop iteration sleeps for 1 second to simulate some work to be done, once the InterruptedException is caught, myThread terminates early when it hasn’t yet finished its execution by printing out some information, restoring the interruption status, and then gracefully returning.

Previous Article
Every support is much appreciated ❤️

Buy Me a Coffee