- 线程状态(简图)
- 线程状态(详细)
- 线程方法
- 线程停止
- 线程礼让 yield
- 线程插队 join
- 线程状态监测
- 线程优先级
- 守护线程 daemon
线程从诞生到使用,再到最后的死亡,大体上符合下列的几种状态转换。
就像一个人的生老病死。
【注意:】
-
1、new Thread() 只是让一个线程诞生,并不会让其运行,此时只是一个简单的Java类。
-
2、调用 t.start(),只会让线程进入就绪状态,并不会立即执行该线程。
原因在于:需要等待CPU的调度。(CPU的切片)
-
3、阻塞状态解除后,并不会立即执行该线程,此时依旧需要等待CPU对其进行调度。
线程状态的切换,大致为以上的逻辑图,如果深究其各种Java的API调用,则可以参考下列逻辑图。
【注意:】
- 1、等待
处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。 - 2、超时等待
处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
其实等待和超时等待都属于阻塞。
线程方法该图逻辑参考:Java线程的6种状态及切换(透彻讲解)
| 方法 | 说明 |
|---|---|
| setPriority(int newPriority) | 设置线程的优先级 |
| static native void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
| void join() | 等待该线程终止 |
| static void yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
| void interrupt() | 中断线程。不建议使用 |
| boolean isAlive() | 测试线程是否处于活动状态。 |
在java.lang.Thread类中,存在类似stop()、destroy()等让线程消亡的方法。但不推荐使用。
【疑问:】如何停止一个线程?
1、建议线程正常停止。使用次数、不建议死循环(使用死循环也需要增加延时,避免CPU卡死)
2、建议使用标志位,让线程自己停止下来。
3、不要使用stop()、destroy()等过时或者JDK不建议的方法。
接下来编写一个案例,来掩饰如何正确的停止线程:
package thread;
import java.util.concurrent.TimeUnit;
public class StopThread implements Runnable {
// 1、设置标识位
boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run ... thread "+ i++ +" flag="+flag);
}
}
// 2、设置公开方法 停止线程
public void stop(){
flag = false;
}
public static void main(String[] args) throws InterruptedException {
StopThread stopThread = new StopThread();
new Thread(stopThread).start();
// 保证线程能够被CPU调度执行
TimeUnit.SECONDS.sleep(5);
for (int i = 0; i < 1000; i++) {
//System.out.println("main i="+i);
if(i == 900){
// 调用stop 停止线程
stopThread.stop();
System.out.println("start stop thread");
//break;
}
}
}
}
其运行效果如下所示:
-
结束线程之前:
-
结束线程后:
Thread类中提供了一种礼让方法,使用yield()方法表示,它只是给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
yield()方法使具有同样优先级的线程有进入可执行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。
对于支持多任务的操作系统来说,不需要调用yeild()方法,因为操作系统会为线程自动分配CPU时间片来执行。
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 线程进行礼让操作后,会由运行状态转化为就绪状态
- 礼让只是让CPU重新进行调度,礼让不一定成功!
接下来,根据代码来演示线程礼让到底是什么样子的:
package thread;
public class YieldThread {
public static void main(String[] args) {
MyYiledThread myYiledThread = new MyYiledThread();
new Thread(myYiledThread,"A").start();
new Thread(myYiledThread,"B").start();
}
}
class MyYiledThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行");
// 通过注释掉下列代码,分别执行礼让的现象
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程结束");
}
}
现象:
-
注释掉 Thread.yield(); 后:
-
不注释 Thread.yield(); 后:
当前线程里调用其它线程1的join(),当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
package thread;
public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(" 线程插队...."+i);
}
}
public static void main(String[] args) throws InterruptedException {
// 创建线程并执行
ThreadJoin threadJoin = new ThreadJoin();
Thread thread = new Thread(threadJoin);
thread.start();
for (int i = 0; i < 500; i++) {
if(i == 200){
// 主线程中,对其他线程进行join()调用
// 让其他线程可以插队
thread.join();
}
System.out.println("main = "+i);
}
}
}
其运行后的状态如下所示:
【疑问:】为什么 插队线程 不是从0全部插入?
因为没有对其他线程进行设定运行时间,当主线程的i还未到200时,此时其他线程和主线程是并行状态。
当主线程中执行了其他线程的join(),也就是相当于主线程让其他线程插队,此时主线程阻塞,让其他线程将剩下的完全执行完成后,主线程才能继续执行!
【扩展:】
假设有一个需求:
需要异步执行一段代码,然后再主线程中获取最终执行的结果。
就像下列代码所示:
package thread;
import java.util.concurrent.TimeUnit;
public class ThreadJoin2 implements Runnable {
private int a = 0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
a ++;
}
}
public int getA() {
return a;
}
public static void main(String[] args) throws InterruptedException {
ThreadJoin2 threadJoin2 = new ThreadJoin2();
Thread thread = new Thread(threadJoin2);
thread.start();
System.out.println(threadJoin2.getA());
// 一般情况下,在时间可控时,采取sleep 对主线程延迟,然后获取最终结果
// TimeUnit.SECONDS.sleep(5);
// System.out.println(threadJoin2.getA());
// 如果线程执行的时间不可控,则需要让主线程调用其他线程的 join()
// 保证主线程阻塞,并让其他线程先执行,直到其他线程执行完毕
thread.join();
System.out.println(threadJoin2.getA());
}
}
线程状态监测
上面已经说到了线程的各个状态变化逻辑信息,以及主要的几个方法讲解,接下来我们来探究一个线程的生命周期是怎么样变化的。
在JDK 1.8的中文文档中,关于线程状态归纳于java.lang.Thread.State这个枚举类中。
下面,以代码的方式说明线程在其整个生命周期内状态的变更现象:
package thread;
import java.util.concurrent.TimeUnit;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
// 让线程执行一定的次数
for (int i = 0; i < 5; i++) {
// 每次延迟1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 循环结束后输出
System.out.println("循环结束....");
});
// 此时的线程未进行start
Thread.State state = thread.getState();
// 输出状态
System.out.println(state);
// 启动线程
thread.start();
state = thread.getState();
System.out.println(state);
// 启动之后,因为run()中是不断的循环,此处写一个循环不断打印其状态信息
// 满足的条件就是:当前线程未消亡
while (state != Thread.State.TERMINATED){
// 延迟打印
TimeUnit.MILLISECONDS.sleep(100);
// 重新赋值
state = thread.getState();
System.out.println(state);
}
}
}
执行上述代码后,控制台输出日志信息如下所示:
NEW RUNNABLE TIMED_WAITING TIMED_WAITING TIMED_WAITING ...... TIMED_WAITING 循环结束.... TERMINATED线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。
线程调度器按照优先级来决定应该调度那个线程来执行。
线程的优先级用整型数字表示,范围为1~10。
【注意:】
优先级高的线程不一定优先执行。
线程的执行,依旧是通过CPU的调度。
只能说优先级越高,线程被调度的权重(几率)越大。
Java中,关于线程优先级的设置和获取为以下方式:
接下来采取代码案例的方式,说明优先级的设置和获取,代码如下所示:
package thread;
// 线程优先级
public class ThreadPriority {
public static void main(String[] args) {
// 主线程默认的优先级
System.out.println(Thread.currentThread().getName()+" --- "+Thread.currentThread().getPriority());
// 获取线程的执行结构体
MyThread myThread = new MyThread();
// 创建多个线程
Thread t1 = new Thread(myThread,"t1");
Thread t2 = new Thread(myThread,"t2");
Thread t3 = new Thread(myThread,"t3");
Thread t4 = new Thread(myThread,"t4");
Thread t5 = new Thread(myThread,"t5");
Thread t6 = new Thread(myThread,"t6");
// 必须先设置优先级再启动才行;否则设置优先级无效
t2.setPriority(1);
t3.setPriority(6);
t4.setPriority(3);
t5.setPriority(Thread.MAX_PRIORITY); // 最大优先级 10
t6.setPriority(8);
t1.start(); // t1 采取默认优先级
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" --- "+Thread.currentThread().getPriority());
}
}
优先级越高,不代表一定优先执行!
线程优先级低,只是意味着获取调度的概率越低,并不是优先级低就不会被调用了。
这里就需要看CPU的调度了。
守护线程 daemon性能倒置
关于线程的划分一般分为如下:
- 线程分为用户线程和守护线程。
- 虚拟机必须确保用户线程执行完毕。----- main 线程
- 虚拟机不用等待守护线程执行完毕。 ----- gc
测试代码如下所示:
package thread;
import java.util.concurrent.TimeUnit;
// 守护进程测试
public class DaemonTest {
public static void main(String[] args) {
MyDaemo myDaemo = new MyDaemo();
MyThread2 myThread2 = new MyThread2();
// 设置守护线程
Thread mdT = new Thread(myDaemo);
mdT.setDaemon(true); // 默认为false,表示用户线程;当设置为false后表示守护线程
mdT.start(); // 启动线程
new Thread(myThread2).start(); // 默认为用户线程
}
}
// 守护线程
class MyDaemo implements Runnable{
@Override
public void run() {
while (true){
System.out.println("守护线程执行中....");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试线程
class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("测试线程执行中.... "+i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
【疑问:】守护线程为什么会终止?
虚拟机不用等待守护线程执行完毕。
当用户线程执行完毕之后,程序也就结束了,此时jvm 虚拟机不会立即关闭,在关闭的过程中,守护线程是还在继续执行的。
当虚拟机完全关闭之后,守护线程也就结束运行了。



