多线程基础(十二):Thread优先级分析

时间:2022-07-25
本文章向大家介绍多线程基础(十二):Thread优先级分析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1.thread中的优先级

在前面学习Thread源码的时候,提到了Thread可以设置优先级。其优先级通过setPriority方法进行设置。

/**
 * The minimum priority that a thread can have.
 */
public final static int MIN_PRIORITY = 1;

/**
 * The default priority that is assigned to a thread.
 */
public final static int NORM_PRIORITY = 5;

/**
 * The maximum priority that a thread can have.
 */
public final static int MAX_PRIORITY = 10;

在Thread中,提供了3种优先级类型。实际上java提供的线程优先级为1-10的整数。按照传统的理解,如果设置为高优先级,那么其线程在执行的过程中得到的CPU执行的机会将大于优先级低的线程。那么下面我们对线程的优先级进行验证。

2.验证

import java.util.concurrent.TimeUnit;

public class PriorityTest extends Thread{

	private volatile long count = 0;

	public long getCount() {
		return count;
	}

	@Override
	public void run() {
		try {
			while (true) {
				count ++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {

		PriorityTest t1 = new PriorityTest();
		PriorityTest t2 = new PriorityTest();
		PriorityTest t3 = new PriorityTest();
		PriorityTest t4 = new PriorityTest();
		t1.setPriority(1);
		t2.setPriority(10);
		t3.setPriority(10);
		t4.setPriority(10);
		t1.start();
		t2.start();
		t3.start();
		t4.start();


		new Thread(() -> {
			while (true) {
				try {
					TimeUnit.SECONDS.sleep(1);
					System.out.println("print:t1["+t1.getCount()+ "] t2["+ t2.getCount()+"] t3["+ t3.getCount()+"] t4["+ t4.getCount()+"]");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}).start();


	}

}

上述代码,在windows系统idea测试输出:

print:t1[141315520] t2[149400119] t3[161544594] t4[153872496]
print:t1[259967363] t2[289447885] t3[292740550] t4[302513889]
print:t1[437426834] t2[481490591] t3[488735422] t4[495629048]
print:t1[637082266] t2[689029535] t3[701732100] t4[703677802]
print:t1[819953148] t2[890855295] t3[905247104] t4[912506447]
print:t1[1018990612] t2[1091062394] t3[1113091363] t4[1119786453]
print:t1[1205206535] t2[1272513475] t3[1294425273] t4[1304756799]
print:t1[1404866545] t2[1470620675] t3[1492622623] t4[1496612016]
print:t1[1590767926] t2[1651400185] t3[1688239699] t4[1699097044]
print:t1[1773328378] t2[1847804448] t3[1883739945] t4[1905774630]
print:t1[1971298149] t2[2041965328] t3[2081918834] t4[2104698757]
print:t1[2157880017] t2[2232498558] t3[2269818261] t4[2306271184]
print:t1[2330479610] t2[2409405215] t3[2452668020] t4[2489070628]
print:t1[2469966402] t2[2558469732] t3[2606285673] t4[2626978898]

在windows系统上可以看出,优先级低的线程会比高优先级的线程获得的执行次数少。那么我们将这个代码放到linux上执行。

print:t1[100414879] t2[99694288] t3[95904584] t4[37197108]
print:t1[171295255] t2[171298328] t3[170851240] t4[64810891]
print:t1[246907489] t2[246964402] t3[243168325] t4[93703194]
print:t1[318778259] t2[320983272] t3[320230850] t4[122450830]
print:t1[397315080] t2[393067515] t3[394953718] t4[151256012]
print:t1[469708806] t2[467779539] t3[469846211] t4[180071710]
print:t1[544945482] t2[542967801] t3[544680871] t4[208462144]
print:t1[614797793] t2[612902001] t3[614233757] t4[236198233]
print:t1[690537087] t2[686338172] t3[684265213] t4[264657155]
print:t1[760772881] t2[759907861] t3[757426387] t4[293023592]
print:t1[834746175] t2[830694617] t3[830638526] t4[321241117]
print:t1[906519160] t2[902894713] t3[902126475] t4[348491969]
print:t1[975998822] t2[976049732] t3[974747744] t4[376682800]
print:t1[1051943748] t2[1046739616] t3[1044848610] t4[405288434]
print:t1[1123128682] t2[1118649942] t3[1116233253] t4[433625044]
print:t1[1189535190] t2[1185455433] t3[1183875916] t4[459984507]
print:t1[1264878289] t2[1260889713] t3[1258093372] t4[488926353]

那么可以看出,t1由于先启动,其执行次数居然比后续优先级高的t2、t3、t4次数多很多。这个线程优先级的策略,在这个地方就失效了。

3.原因分析

实际上,这涉及到了java的线程调度。我们在前面学习的过程中知道,java线程实际上就是操作系统中的线程。这个关系是一一对应的。一个java线程实际上就是操作系统中的线程。那么线程调度,实际上就是由操作系统进行控制的。jvm本身的线程优先级,只是一个建议性的结果。VM规范中规定每个线程都有优先级,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。JVM的规范没有严格地给调度策略定义。 而线程调度的模式分为两种——抢占式调度和协同式调度。 抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,线程的切换不由线程本身决定,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的阻塞不会导致整个进程阻塞。 协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行,线程的执行时间由线程本身控制,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直阻塞,那么可能导致整个系统阻塞挂起。 参考网上的一张图:

因此,java实际上就是使用的是抢占式线程调度。需要注意的是,我们在前面聊中断机制的时候,java是使用的是协调式中断。这与线程调度有很大的不同。 那么由此可以知道,java的线程优先级,实际上取决于操作系统如何进行线程调度。如果操作系统采用了线程的优先级状态然后来调度,那么这个优先级就是生效的,反之则不一定有用。

4.总结

通过本文,我们可以知道,java中的线程优先级,实际上取决于操作系统是如何实现的。这存在很大的不确定性。因此我们在编码的过程中,不要依赖于这个优先级进行编程。否则可能在某些操作系统中就会导致失效。