For a long time, multithreading has been favored by interviewers. Although I personally think few of us really get the chance to develop complex multithreaded applications (I've had an opportunity over the past seven years), understanding multithreading can be very helpful to boost your confidence. Before, I discussed a difference between wait() and sleep() methods, and this time I'll discuss the difference between join() and yield(). Frankly speaking, I haven't actually used any of these methods, so if you feel something wrong, please discuss it.
Background of Java thread scheduling
In a variety of threads, the Java virtual machine must implement a priority-based scheduler. This means that each thread in a Java program is assigned a certain priority, represented by a positive integer in a defined range. Priorities can be changed by developers. Even if the thread has been running for some time, the Java virtual machine will not change its priority.
The value of priority is important because the Convention between the Java Virtual Machine and the underlying operating system is that the operating system must select the highest priority Java thread to run. So let's say that Java implements a Priority-based Scheduler . The scheduler is implemented in a priority manner, which means that when a thread with higher priority arrives, it will be interrupted (preempted) regardless of whether the low priority thread is running or not. This convention is not always the case for the operating system, which means that the operating system may sometimes choose to run a lower priority thread. (I hate multithreading because it doesn't guarantee anything)
Note that Java does not limit threads to run in time slices, but most operating systems do. Often confused in terminology: preemption is often confused with time slices. In fact, preemption means that only threads with high priority can take precedence over threads with low priority, but when threads have the same priority, they cannot preempt each other. They are usually controlled by time slices, but that's not what Java requires.
Understanding Thread Priorities
Next, understanding thread priority is an important step in multithreading learning, especially the working process of yield() function.
- Remember that when the priority of a thread is not specified, all threads carry a normal priority.
- Priorities can be specified in the range of 1 to 10. 10 is the highest priority, 1 is the lowest priority, and 5 is the ordinary priority.
- Keep in mind that the thread with the highest priority is given priority in execution. But there is no guarantee that threads will be running at startup.
- Threads currently running may always have a higher priority than those waiting in the thread pool to run.
- It is up to the scheduler to decide which thread is executed.
- t.setPriority() is used to prioritize threads.
- Remember that the thread priority should be set before the thread start method is invoked.
- You can use constants such as MIN_PRIORITY,MAX_PRIORITY, NORM_PRIORITY to set priorities.
Now, when we have some understanding of thread scheduling and thread priority, let's move on to the topic.
yield() method
In theory, yield means letting go, giving up, surrendering. A thread calling yield() tells the virtual machine that it is willing to let other threads occupy its own place. This indicates that the thread is not doing something urgent. Note that this is only a hint, and there is no guarantee that it will not have any impact.
In Thread.java yield() is defined as follows:
/** * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU. * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect. */ public static native void yield();
Let US enumerate some important points about the above definition:
- Yield is a static native method
- Yield tells currently executing threads to give running opportunities to threads with the same priority in the thread pool.
- Yield does not guarantee that currently running threads will be swiftly converted to a runnable state
- It only enables a thread to move from a running state to a runnable state, not a waiting or blocking state.
yield() method use example
In the following example program, I randomly created two threads named producer and consumer. The producer set the minimum priority and the consumer set the highest priority. In the case of Thread.yield() comments and non-comments, I will run the program separately. When the yield() method is not invoked, although the output sometimes changes, usually the consumer prints it first and then the producer.
When the yield() method is invoked, the two threads print in turn, and then give each other the opportunity to execute, so on.
public class YieldExample { public static void main(String[] args) { Thread producer = new Producer(); Thread consumer = new Consumer(); producer.setPriority(Thread.MIN_PRIORITY); //Min Priority consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority producer.start(); consumer.start(); } } class Producer extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("I am Producer : Produced Item " + i); Thread.yield(); } } } class Consumer extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("I am Consumer : Consumed Item " + i); Thread.yield(); } } }
The output of the above program without calling yield() method:
I am Consumer : Consumed Item 0 I am Consumer : Consumed Item 1 I am Consumer : Consumed Item 2 I am Consumer : Consumed Item 3 I am Consumer : Consumed Item 4 I am Producer : Produced Item 0 I am Producer : Produced Item 1 I am Producer : Produced Item 2 I am Producer : Produced Item 3 I am Producer : Produced Item 4
The output of the above program when the yield() method is called:
I am Producer : Produced Item 0 I am Consumer : Consumed Item 0 I am Producer : Produced Item 1 I am Consumer : Consumed Item 1 I am Producer : Produced Item 2 I am Consumer : Consumed Item 2 I am Producer : Produced Item 3 I am Consumer : Consumed Item 3 I am Producer : Produced Item 4 I am Consumer : Consumed Item 4
join() method
The join() method of a thread instance enables one thread to execute after another thread has finished. If the join() method is called on a thread instance, the currently running thread will block until the thread instance has completed execution.
//Waits for this thread to die. public final void join() throwsInterruptedException
Setting a timeout in the join() method makes the impact of the join() method invalid after a specific timeout. When timeouts occur, the main method and task threads apply to run equally. However, when it comes to sleep, the join() method depends on the operating system for timing, so you should not assume that the join() method will wait for the time you specify.
Like sleep,join responds to interruptions by throwing InterruptedException.
join() method use example
package test.core.threads; public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { public void run() { System.out.println("First task started"); System.out.println("Sleeping for 2 seconds"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("First task completed"); } }); Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("Second task completed"); } }); t.start(); // Line 15 t.join(); // Line 16 t1.start(); } } Output: First task started Sleeping for 2 seconds First task completed Second task completed
These are small but important concepts. Let me know what you think in the comments section.