1、线程的运行原理
我们都知道虚拟机是由:堆、栈、方法区等组成,那么其中的栈内存是给谁使用的呢?
1)、每个线程启动后,虚拟机就会为其分配一块栈内存
2)、每个栈由多个栈帧(Frame)组成,对应的就是每次方法调用时所占用的内存
3)、每个线程只能有一个活动的栈帧,对应着当前正在执行的那个方法
线程上下文切换
因为如下的一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码
1)、线程的cpu时间片用完了
2)、垃圾回收,有更高优先级的线程需要运行
3)、线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法。
当线程上下文发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,java中对应的概念就是程序计数器(program counter register),它的作用就是记住下一条jvm指令的执行地址,是线程私有的。
2、线程的状态
从java API层面来看,根据Thread.State枚举,线程分为6种状态
1)、新建状态(new)
创建了一个线程,但是还没有调用start()方法
如:通过Runnable接口创建一个线程类,new一个实例出来,线程就进入了初始状态。
2)、运行状态(Runnable)
java中将就绪(ready)和运行中(running)两种状态统称之为运行状态
那么什么是就绪状态?
就绪说明你有资格运行,调度程序没有挑选到你,你就永远处于就绪状态;
调用线程的start()方法,此时线程就进入了就绪状态;
当前线程时间片用完了,调用当前线程的yield()方法,当前线程就进入了就绪状态
当前线程sleep()方法结束,其它线程join()结束,等待用户输入完毕,某个线程就拿到了对象锁,这些线程也将进入就绪状态
锁池里的线程拿到对象锁后,也进入就绪状态。
运行中状态(running):
线程调度程序从可运行池中选择一个线程作为当前线程所处的状态,这也是线程进入运行状态的唯一的一种方式。
3)、阻塞状态(blocked)
表示线程阻塞于锁;阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块时(获取锁)的状态
4)、等待状态(waiting)
进入该状态的线程需要等待其它线程作出一些特定的动作(如通知或中断)
进入这种状态的线程不会被分配cpu执行时间片,它们要等待被显示的唤醒,否则会处于无限等待的状态。
5)、超时等待状态(timed_waiting)
该状态不同于waiting,它可以在指定的时间后自行返回,处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其它线程显示的唤醒,在达到一定时间后他们会自动唤醒。
6)、终止状态(terminated)
表示该线程已经执行完毕。
3、线程的状态转换
1)、新建状态(new):新创建了一个线程对象
2)、就绪状态(runnable):线程对象创建后,其它线程调用了该对象的start()方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3)、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
4)、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分为三种:
等待阻塞:运行的线程执行了wait()方法,JVM会将该线程放入等待池中
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
其它阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
4、线程调度
4.1、线程的优先级
1)、java的线程有优先级,优先级高的线程获得较多的运行机会。
2)、java线程的优先级用整数表示,取值范围为1~10,Thread类有如下三个静态常量
static int MAX_PRIORITY:线程可以具有的最高优先级,取值为10
static int MIN_PRIORITY:线程可以具有的最低优先级,取值为1
static int NORM_PRIORITY:分配给线程的默认优先级,取值为5
3)、Thread类的setPriority()和getPriority()方法分别用了设置和获取线程的优先级
4)、每个线程都有默认的优先级,主线程的默认优先级为Thread.NORM_PRIORITY
5)、线程的优先级有继承关系,比如:A线程中创建了B线程,那么B将和A具有相同的优先级
6)、JVM提供了10个线程优先级,党羽常见的操作系统都不能很好的映射,如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类的三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
4.2、线程睡眠:sleep()
Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
1)、sleep()方法属于Thread类,主要作用是让当前线程停止执行,将cpu让给其它线程执行。如果有锁,不会释放锁,等睡眠期满之后,恢复为可运行状态,等到切换为运行状态时继续运行。
2)、线程睡眠到期后自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间,因此,sleep()方法不能保证该线程睡眠到其后就开始执行。
3)、
Thread.state; //静态方法
sleep(long millis); //1秒=1000ms
sleep(long millis,int nanos); //millis是毫秒,nanos是纳秒
4)、为什么要让线程睡眠?因为线程执行太快,或者需要强制进入下一轮。
睡眠位置:为了让其它线程有机会执行,可以将Thread.sleep()的调用放在线程run()之内,这样才能保证该线程在执行过程中会睡眠
package com.wzy.day08;
public class TestSleep {
public static void main(String[] args) {
MyThread myThread=new MyThread("wzy");
myThread.start();
for(int i=0;i<10;i++){
System.out.println("i am Main Thread");
}
}
}
class MyThread extends Thread{
MyThread(String name){
super(name);
}
@Override
public void run() {
for(int i=1;i<=10;i++){
System.out.println("i'm "+getName());
}
}
}
线程睡眠到期自动苏醒,并返回到可运行状态,而不是运行状态
4.3、线程等待:wait()
Object类中的wait()方法,导致当前线程等待,直到其它线程调用此对象的notify()方法或者notifyAll()唤醒方法。这两个唤醒方法也是Object类中的方法,行为等价于调用wait(0)一样
4.4、线程让步:yield()
Thread.yield()方法,用于暂停当前正在执行的线程对象,将执行机会让给相同或者更高优先级的线程
4.5、线程加入:join()
join()方法,等待其它线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
4.6、线程唤醒:notify()、notifyAll()
Object的notify()方法,唤醒在此对象监视器上等待的单个线程。
如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在实现做出决定时发生。
线程通过调用其中一个wait()方法