For a long time, multi-threading questions have been favored by interviewers. While I personally believe that few of us actually get the chance to develop complex multi-threaded applications (I've gotten one in the past seven years), understanding multi-threading can be useful in increasing your confidence. Previously, I discussed the difference between wait() and sleep() methods. This time, I will discuss the difference between join() and yield() methods. To be honest, I haven't actually used any of these methods, so if you feel something is inappropriate, please discuss it.
A little background on Java thread scheduling
In various threads, the Java virtual machine must implement a prioritized, priority-based scheduler. This means that each thread in a Java program is assigned a certain priority, represented by a positive integer within a defined range. Priorities can be changed by developers. Even if the thread has been running for a certain period of time, the Java virtual machine will not change its priority. The priority value is important because the agreement between the Java virtual machine and the underlying operating system is that the operating system must choose the one with the highest priority. Java threads run. So we say that Java implements a priority-based scheduler. The scheduler is implemented in a priority manner, which means that when a thread with a higher priority arrives, it will be interrupted (preempted) regardless of whether the lower-priority thread is running. This convention is not always true for the operating system, which means that the operating system may sometimes choose to run a lower priority thread. (I hate this aspect of multithreading because it doesn't guarantee anything)
Note that Java does not restrict threads to run in time slices, but most operating systems do. Common confusion in terminology: preemption is often confused with time slicing. In fact, preemption means that only threads with high priority can execute before threads with low priority, but when threads have the same priority, they cannot preempt each other. They are usually time-sliced, but this is not a requirement of Java.
Understand thread priority
Next, understanding thread priority is an important step in multi-threading learning, especially understanding the working process of the yield() function.
Remember that when a thread's priority is not specified, all threads carry normal priority.
Priority can be specified with a range from 1 to 10. 10 represents the highest priority, 1 represents the lowest priority, and 5 is the normal priority.
Remember that the thread with the highest priority is given priority when executing. However, there is no guarantee that the thread will enter the running state when it is started.
The currently running thread may always have a higher priority than the thread waiting for a chance to run in the thread pool.
The scheduler decides which thread is executed.
t.setPriority() is used to set the priority of the thread.
Remember that the priority of the thread should be set before the thread start method is called.
You can use constants such as MIN_PRIORITY, MAX_PRIORITY, NORM_PRIORITY to set the priority
Now, when we have a certain understanding of thread scheduling and thread priority, let us enter the topic.
yield() method
Theoretically, yield means letting go, giving up, surrendering. A thread calling the yield() method tells the virtual machine that it is willing to let other threads occupy its position. This indicates that the thread is not doing something urgent. Note that this is only a hint and does not guarantee that it will not have any impact.
yield() is defined as follows in Thread.java:
/** * 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 list some important points about the above definition:
Yield is a static native method
Yield tells the currently executing thread to The running opportunity is given to threads with the same priority in the thread pool.
Yield does not guarantee that the currently running thread will quickly transition to a runnable state
It can only make a thread move from a running state to a runnable state, rather than a waiting or blocking state
yield() method usage example
在下面的示例程序中,我随意的创建了名为生产者和消费者的两个线程。生产者设定为最小优先级,消费者设定为最高优先级。在Thread.yield()注释和非注释的情况下我将分别运行该程序。没有调用yield()方法时,虽然输出有时改变,但是通常消费者行先打印出来,然后事生产者。
调用yield()方法时,两个线程依次打印,然后将执行机会交给对方,一直这样进行下去。
package test.core.threads; 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(); } } }
上述程序在没有调用yield()方法情况下的输出:
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
上述程序在调用yield()方法情况下的输出:
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()方法
线程实例的方法join()方法可以使得在另一个线程的执行结束后再开始执行这个线程。如果join()方法被在一个线程实例上调用,当前运行着的线程将阻塞直到线程实例完成了执行。
//Waits for this thread to die. public final void join() throws InterruptedException
在join()方法内设定超时,使得join()方法的影响在特定超时后无效。当超时时,主方法和任务线程申请运行的时候是平等的。然而,当涉及sleep时,join()方法依靠操作系统计时,所以你不应该假定join()方法将会等待你指定的时间。
像sleep,join通过抛出InterruptedException对中断做出回应。
join()方法使用示例
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