线程调度和时间片
现代操作系统提供了强大的线程管理能力,因此Java不需要单独的进行线程的管理和调度,而是将线程调度工作委托给操作系统去完成。在某些操作系统中(Solaris)中,JVM中的线程和操作系统的本地线程甚至可以做到一一对应起来。
由于cpu的计算频率相当的高,每秒钟计算几十亿次。因此现代操作系统都是以毫秒为维度对cpu进行分片,然后通过时间分片进行线程的调度和管理。window XP 时间片的长度是20ms。
当前各个操作系统主流的调度模型为 “抢占式调度模型”,即优先级较高的线程,会有限分配cpu时间片。关于 “抢占式调度模型” 的详细信息,可以在操作系统原理中找寻到。
JAVA线程的创建方式
在JAVA中,可以通过下面3种方式来创建线程:
1、集成Thread类;
2、实现Runnable接口;
3、实现Callable接口;
package com.java.thread.demo001;
import java.util.concurrent.*;
/**
* 1、java实现线程的3种方式 Thread Runnable Callable
* */
public class Demo001_a {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " main 线程");
new ThreadTest().start();
new Thread(new RunnableTest()).start();
//callable 线程启动
try {
CallableTest callable = new CallableTest();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(callable);
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "通过Thread类实现线程");
}
}
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "通过Runnable类实现线程");
}
}
class CallableTest implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "通过Callable类实现线程");
return "callable 线程";
}
}
JAVA线程的状态
在java中一个线程从创建到消亡,需要经历多种不同的状态,这个也称为线程的生命周期。
线程的生命周期大概如下图所示:
NEW: 线程被构建,但是还没有调用start()方法。
RUNNABLE:可运行状态,可运行状态包括运行中状态和就绪状态。
BLOCKED:阻塞状态,等待其他线程释放锁。
WAITING: 等待状态,需要等待其他线程对其进行通知或者中断等操作,从而进入下一个状态。
TIME_WAITING:超时等待状态,可以在一定时间自行返回。
TERMINATED:终止状态,当前线程执行完毕。
下面我们通过程序来验证线程的生命周期。
package com.java.thread.demo001;
import java.util.concurrent.*;
public class Demo001_b {
public static void main(String[] args) {
new Thread(new WaitingTime(),"WaitingTime").start();
new Thread(new WaitingState(),"WaitingState").start();
//先启动,并且抢占到锁 且不释放,TIMED_WAITING 状态
new Thread(new BlockedThread(),"BlockedThread-1").start();
//后启动,等待BlockedThread-1 释放锁, BLOCKED 状态
new Thread(new BlockedThread(),"BlockedThread-2").start();
}
}
/**
* java.lang.Thread.State: TIMED_WAITING (sleeping)
* 1、超时等待状态、可以在一定的时间自行返回;
* */
class WaitingTime implements Runnable{
@Override
public void run() {
while(true){
waitSecond(2000);
}
}
//线程等待多少秒
public static final void waitSecond(long seconds){
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* java.lang.Thread.State: WAITING (on object monitor)
* 1、WaitingState 这个类无论创建多少个实例,
* synchronized 锁都是同一个。
* 2、WaitingState.class.wait() 进入 WAITING 状态。
* */
class WaitingState implements Runnable{
@Override
public void run() {
while(true){
synchronized (WaitingState.class){
try {
WaitingState.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class BlockedThread implements Runnable{
@Override
public void run() {
synchronized (BlockedThread.class){
while(true){
System.out.println(Thread.currentThread().getName() + " 抢占锁");
WaitingTime.waitSecond(10);
}
}
}
}
通过jvm工具来查看线程的状态
C:\Users\WIN>jps
12736
2336
23760 NailgunRunner
20100 Launcher
24980 Launcher
12552 Jps
22220
24428 Demo001_b
C:\Users\WIN>jstack 24428
JAVA线程的执行顺序
1、线程的执行顺序是不确定的,也就是说每次调用多个线程的结果是不确定的。
package com.java.thread.demo001;
import java.util.concurrent.TimeUnit;
/**
* 1、线程的执行顺序;
* 线程的执行顺序是无法确定的,也就是说每次执行的结果过都是不一样的。
* */
public class Demo001_c {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println("thread1..");
});
Thread thread2 = new Thread(() -> {
System.out.println("thread2..");
});
Thread thread3 = new Thread(() -> {
System.out.println("thread3..");
});
thread1.start();
thread2.start();
thread3.start();
}
}
//第一次运行结果如下:
thread1..
thread3..
thread2..
//第二次运行结果如下
thread2..
thread3..
thread1..
2、如果想要多线程按照一定的顺序来执行,可以使用 join 来实现。
package com.java.thread.demo001;
import java.util.concurrent.TimeUnit;
/**
* 1、线程的执行顺序;
* 线程的执行顺序是无法确定的,也就是说每次执行的结果过都是不一样的。
* */
public class Demo001_c {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println("thread1..");
});
Thread thread2 = new Thread(() -> {
System.out.println("thread2..");
});
Thread thread3 = new Thread(() -> {
System.out.println("thread3..");
});
thread1.start();
thread1.join(); //让主线程等待子线程完成
thread2.start();
thread2.join();
thread3.start();
thread3.join();
}
}
//运行结果如下:
thread1..
thread2..
thread3..
3、我们来思考一下:join是如何来保证线程的顺序执行的了?
通过追踪join方法的代码,我们发现其是通过JNI调用JDK底层方法wait()来实现的。
调用wait方法会使得主线程处于等待状态,直到子线程结束之后,才执行下一步指令。
线程的优先级
在Thread类中有一个方法可以设置线程的优先级,setPriority(int i ); i的值为 1-10, 最大优先级为10, 最小优先级为1,默认优先级为5。
下面我们通过一个实验来验证一下java中的优先级。
public class Demo002_d {
public static void main(String[] args) {
testThreadPriority();
}
/**
* @desc 多线程优先级测试
* @desc 思路:启动10个线程做计数器累加,线程的优先级分别为 1-10 , 1 分钟后结束。
* 计数器的值越大, 则说明该线程抢到的cpu时间片越多。
* */
public static void testThreadPriority(){
TestThreadPriority[] threads = new TestThreadPriority[10];
for(int i=0; i<10;i++){
TestThreadPriority thread = new TestThreadPriority();
threads[i] = thread;
threads[i].setName("thread-" + i);
threads[i].setPriority(i+1);
}
//启动线程
for(int i=0; i<10;i++){
threads[i].start();
}
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//停止线程
for(int i=0; i<10;i++){
threads[i].stop();
}
for(int i=0; i<10;i++){
System.out.println(threads[i].getName()
+ ": "
+ threads[i].acounter);;
}
}
static class TestThreadPriority extends Thread{
public Long acounter = 0L;
@Override
public void run() {
while(true){
acounter ++;
}
}
}
}
返回结果如下:
thread-0: 21266406
thread-1: 21563155
thread-2: 115825951
thread-3: 87804208
thread-4: 356981196
thread-5: 265969365
thread-6: 618522239
thread-7: 513331872
thread-8: 664392897
thread-9: 764687800
可以观察到,优先级越高其抢占到的cpu执行时间片就越多,这种情况并不是绝对的。但是大体上是无限接近这种情况的。这也侧面印证了cpu执行顺序是无序的。
参考
<<深入理解高并发编程>>