一、线程状态
1-新建状态 ( New ):
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
2-就绪状态 ( Runnable ):
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
3-运行状态 ( Running ):
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
4-阻塞状态 ( Blocked ):
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。sleep() 不会释放持有的同步锁
5-死亡状态 (Dead):
一个运行状态的线程完成任务或者因异常退出了run()方法,该线程就切换到终止状态。死亡状态的线程不可以再执行
线程状态转换图:
二、线程调度
Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。
线程优先级:java中的线程是具有优先级的,优先级高的线程会获得更多的运行机会。
Java中线程的优先级是用整数来表示的,取值范围是1-10
Thread类中有三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
可以用Thread类的setPriority和getPriority方法分别来设置和获取线程的优先级
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
注意:线程优先级不能保证线程执行的顺序,只能说明优先级高的线程更加重要
三、常用方法归纳
1、Thread.sleep(long millis) —— 线程睡眠
sleep方法是Thread类提供静态方法,表示始终让当前正在执行的线程暂停一段时间,并进入阻塞状态。使用时不要用实例对象调用,只会让当前的线程进入睡眠,而不是使调用该方法的线程对象进入睡眠。
当睡眠的时间结束,线程重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的控制,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
2、Thread.yield() —— 线程让步
yield()方法也是Thread类提供静态方法,它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器也可能将其调度出来重新进入到运行状态执行。
实际上,当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉cpu调度线程。用法如下:
class MyThread extends Thread {
public MyThread(String name, int pro) {
super(name);// 设置线程的名称
this.setPriority(pro);// 设置优先级
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(this.getName() + "线程第" + i + "次执行!");
if (i % 5 == 0)
Thread.yield();
}
}
}
public class LifecycleThread {
public static void main(String[] args) throws InterruptedException {
new MyThread("低级", 1).start();
new MyThread("中级", 5).start();
new MyThread("高级", 10).start();
}
}
关于sleep()方法和yield()方的区别如下:
①、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。
②、sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。
③、sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的执行。
sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 IO 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
3、join —— 等待线程终止
它不是静态方法,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能。
为什么要用join()方法?
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
不加join代码如下:
class MyThread extends Thread {
public MyThread(String name, int pro) {
super(name);// 设置线程的名称
this.setPriority(pro);// 设置优先级
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + "第" + i + "次执行!");
}
}
}
public class LifecycleThread {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始");
MyThread t1 = new MyThread("子线程", 10);
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行代码");
}
System.out.println("主线程结束");
}
}
主线程开始
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
子线程第0次执行!
子线程第1次执行!
子线程第2次执行!
子线程第3次执行!
主线程结束
子线程第4次执行!
子线程第5次执行!
子线程第6次执行!
子线程第7次执行!
子线程第8次执行!
子线程第9次执行!
这个输出结果只是其中的一种情况,主线程执行不会等待子线程。
加 join 代码如下:
class MyThread extends Thread {
public MyThread(String name, int pro) {
super(name);// 设置线程的名称
this.setPriority(pro);// 设置优先级
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + "第" + i + "次执行!");
}
}
}
public class LifecycleThread {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始");
MyThread t1 = new MyThread("子线程", 10);
t1.start();
t1.join();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行代码");
}
System.out.println("主线程结束");
}
}
主线程开始
子线程第0次执行!
子线程第1次执行!
子线程第2次执行!
子线程第3次执行!
子线程第4次执行!
子线程第5次执行!
子线程第6次执行!
子线程第7次执行!
子线程第8次执行!
子线程第9次执行!
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程执行代码
主线程结束
主线程一定会等子线程都结束了才结束。
4、interrupt()
它只是向线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
5、线程类的一些常用方法:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
四、多线程使用的时机
多线程的目的是让程序并发执行,改变原有的串行执行方式,来提高效率,当程序中有两个子系统需要并发执行的时候,就需要使用多线程。
多线程是可以编写出高效率的程序,但是一定要注意,太多的线程却会让程序的执行效率实际上降低,因为线程间的切换也是对CPU资源有开销的,过多的线程会使CPU在上下文(线程之间)切换的时间大于程序执行的时间。
五、守护线程
Java线程分两类:
1.用户线程:运行在前台,执行具体任务(例如:主线程,连接网络的子线程等)。
2.守护线程:运行在后台,为其他前台线程服务,
注意:一旦所有用户线程结束运行,守护线程会随JVM一起结束工作;最常见守护线程:垃圾回收线程;数据库连接池监测线程;JVM启动的监测线程。
Ps:如何设置守护线程:可以通过调用Thread类的setDaemon(ture)方法来设置当前的线程为守护线程。