Thread 类的基本用法JavaEE
Posted 奋斗小温
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thread 类的基本用法JavaEE相关的知识,希望对你有一定的参考价值。
目录
📖java 在进行多线程编程时,java标准库提供了一个类 Thread 能够表示一个线程
🥇一、创建线程
1️⃣使用继承 Thread,重写 run 的方法来创建线程 ————使用Thread的run描述线程入口
class MyThread extends Thread
@Override
public void run()
System.out.println("hello world");
public class ThreadDemo1
public static void main(String[] args)
Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例
t.start();//启动线程,在进程中搞了另外一个流水线,新的流水线开始并发的执行另外一个逻辑了
点击运行程序,其实是idea对应的进程,创建了一个新的java进程,这个java进程来执行咱们自己写的代码
这个java进程中有两个线程,一个是main,一个是t上述代码涉及到两个线程:
1.main方法所对应的线程(一个进程中至少得有一个线程),也可以称为主线程
2.通过 t.start 创建新的线程
多线程
class MyThread extends Thread
@Override
public void run()
while (true)
System.out.println("hello t");
public class ThreadDemo1
public static void main(String[] args)
Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例
t.start();//启动线程,在进程中搞了另外一个流水线,新的流水线开始并发的执行另外一个逻辑了
//点击运行程序,其实是idea对应的进程,创建了一个新的java进程,这个java进程来执行咱们自己写的代码
//这个java进程中有两个线程,一个是main,一个是t
//上述代码涉及到两个线程
//1.main方法所对应的线程(一个进程中至少得有一个线程),也可以称为主线程
//2.通过 t.start 创建新的线程
while (true)
System.out.println("hello main");
此时可以看到的效果是,hello t 和 hello main 都能打印出来
此时 t.start(); 另外启动了一个执行流,新的执行流(新的线程)来执行 System.out.println("hello t");
单线程
class MyThread extends Thread
@Override
public void run()
while (true)
System.out.println("hello t");
public class ThreadDemo1
public static void main(String[] args)
Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例
t.run();
while (true)
System.out.println("hello main");
run 不会创建新的线程,run是在 main 线程中执行的,所以会一直输出hello t死循环
此时,代码没有创建其他线程,两个死循环都在同一个线程中的,执行到底一个死循环,代码就出不来了,第二个循环进不去了
class MyThread extends Thread
@Override
public void run()
while (true)
System.out.println("hello t");
try
sleep(1000);//sleep是Thread 的静态方法,参数单位是ms
catch (InterruptedException e) //打断/中断————意思就是sleep睡眠过程中,还没到点就提前唤醒了
e.printStackTrace();
//run 不是一个随便的方法,是重写了父类的方法
//这种重写一般就是功能的扩展,一般这样的重写方法是不需要咋们自己手动调用,已经有其他代码来调用了
//run 可以称为是一个特殊的方法,线程的入口
public class ThreadDemo1
public static void main(String[] args) throws InterruptedException
Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例
t.start();//启动线程,在进程中搞了另外一个流水线,新的流水线开始并发的执行另外一个逻辑了
while (true)
System.out.println("hello main");
try
sleep(1000);//sleep是Thread 的静态方法,参数单位是ms
catch (InterruptedException e) //打断/中断————意思就是sleep睡眠过程中,还没到点就提前唤醒了
e.printStackTrace();
此时打印是交替出现,但并不是真正的交替,每一秒过后,是先打印main还时先打印t是不确定的
多线程在CPU上调度执行的顺序是不确定(随机的)
2️⃣使用实现 Runnable,重写 run 的方法来创建线程 ————使用 Runnable interface 来描述线程入口
class MyRunnable implements Runnable //Runnable字面意思是可运行的,使用Runnable 描述一个具体的任务,通过 run 方法来描述
@Override
public void run()
while (true)
System.out.println("hello t");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//使用 Runnable interface 来描述线程入口
public class ThreadDemo2
public static void main(String[] args)
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
while (true)
System.out.println("hello main");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
这两种方法没有本质区别,第一种是使用Thread的run描述线程入口,第二种是使用 Runnable interface 来描述线程入口
3️⃣继承 Thread ,使用匿名内部类
public class ThreadDemo3
public static void main(String[] args)
Thread t = new Thread()
@Override
public void run()
while (true)
System.out.println("hello t");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
;
t.start();
while (true)
System.out.println("hello main");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
4️⃣继承 Runnable,使用匿名内部类(定义在类里边的类)
public class ThreadDemo4
public static void main(String[] args)
Thread t = new Thread(new Runnable()
@Override
public void run()
while (true)
System.out.println("hello t");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
);
t.start();
while (true)
System.out.println("hello main");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
5️⃣使用 lambda 表达式
lambda 表达式,本质就是一个匿名函数(没有名字的函数,这种一般都是一次性的),java里边函数(方法)是无法脱离类的,在java里边lambda就相当于是一个例外
lambda 表达式的基本写法: (放参数,如果是一个参数,()可以省略) -> 放函数体,如果只有一行代码,也可以省略
public class ThreadDemo5
public static void main(String[] args)
Thread t = new Thread( () ->
while (true)
System.out.println("hello t");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
);
t.start();
while (true)
System.out.println("hello main");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
打印:hello main hello t hello main hello t hello t hello main hello t hello main 循环
🎒二、Thread 的常用属性
📒1.启动一个线程————start()
start 方法:真正从系统这里,创建一个线程,新的线程将会执行 run 方法
run 方法:表示了线程的入口方法是啥(线程启动起来,要执行哪些逻辑)(不是让程序员调用的,要交给系统去自动调用)
给线程中设定一个结束标志位:
public static void main(String[] args)
Thread t = new Thread(() ->
while (true)
System.out.println("hello t");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
);
t.start();
此时这个代码为死循环,导致入口方法无法执行完毕,自然不能结束线程==========》 把循环条件用一个变量来控制
📕2.中断一个线程
🌈中断这里就是字面意思,就是让一个线程停下来,线程的终止。本质上来说,让一个线程终止办法就一种,让该线程的入口方法执行完毕:
public class ThreadDemo1
public static boolean isQuit = false;
public static void main(String[] args)
Thread t = new Thread(() ->
while (!isQuit)
System.out.println("hello t");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("t 线程终止");
);
t.start();
//在主线中,修改 isQuit
try
Thread.sleep(3000);
catch (InterruptedException e)
e.printStackTrace();
isQuit = true;
//输出:
//hello t
//hello t
//hello t
//t 线程终止
🙈如果把 isQuit 从成员变量改到局部变量 main,改代码是否能正常运行?
🙉不能————变量捕获
lambda 表达式能否访问外面的局部变量?可以————变量捕获语法规则
java要求变量捕获,捕获的是变量必须是 final 或者 “实际final”(变量没有用 final 修饰,但是代码中并没有做出修改)
没有final,但是最后修改isQuit,所以不可以改到局部变量 main
但是可以直接变成成员变量————lambda访问成员变量不受变量捕获的规则限制
当前我们使用自己创建的变量来控制循环;Thread 类内置了一个标志位,让我们更方便地实现上述效果
多线程代码顺序不是“从上到下”,而是每个线程都是独立执行的
执行到 start 方法,代码兵分两步:主线程继续往下执行,新线程进入到 run 方法执行labmda 表达式的内容就是新线程 run 方法的内容
public class ThreadDemo2
public static void main(String[] args)
Thread t = new Thread(() ->
//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t
// isInterrupted 就是t对象自带的一个标志位————初始标志位位false
while (!Thread.currentThread().isInterrupted())
System.out.println("hello t");
try ============> run 方法(并发执行)
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//注意!!! 一个非常重要的问题!!! 当 sleep 被唤醒的时候,sleep会自动的把 isInterrupted 标志位给清空 (true -> false)
//break;
//如果需要结束循环,需要在catch中加入break
//为什么 sleep 要清空标志位呢?
//目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制
);
t.start();
//执行到 start 方法,代码兵分两步:主线程继续往下执行,新线程进入到 run 方法执行
try
Thread.sleep(3000);
catch (InterruptedException e) ============> 主线程(并发执行)
e.printStackTrace();
//把 t 内部的标志位给设置成 true
t.interrupt();
//interrupt方法的作用:
//1.设置标志位为true,2.如果该线程正在阻塞(比如在执行sleep),此时就会把阻塞状态唤醒,通过抛出异常的方法让sleep立即结束
//为什么 sleep 要清空标志位呢?
//目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制
//当前,interrupt 方法,效果不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要结束,
//立即结束还是等会结束,都是代码来灵活控制的
//interrupt 只是通知,而不是“命令”
//输出结果:
/*hello t
hello t
hello t
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method) =====》 e.printStackTrace();//打印当前位置的调用栈
at Thread.ThreadDemo2.lambda$main$0(ThreadDemo2.java:19)
at java.lang.Thread.run(Thread.java:748)
hello t
hello t
hello t
hello t*/
此时发现3s时间到,调用 t.interrupt 方法的时候,线程并没有真的就结束了,而是打印了异常信息,又继续执行了
interrupt方法的作用:
1.设置标志位为true,2.如果该线程正在阻塞(比如在执行sleep),此时就会把阻塞状态唤醒,通过抛出异常的方法让sleep立即结束注意!!! 一个非常重要的问题!!! 当 sleep 被唤醒的时候,sleep会自动的把 isInterrupted 标志位给清空 (true -> false)
这里只有第一次抛异常,第二次就不会抛异常:
首先 isInterrupted 的初始标志位为 false ======> 如果 sleep 执行的时候看到这个标志位为 false, sleep 正常进行休眠操作【t.interrupt();】 如果当前标志位为 true ,sleep 无论是刚刚执行完是已经执行了一半,都会触发两件事 1.立即抛出异常 2.清空标志位为 false
再下次循环到 sleep ,由于当前标志位本身就是 false,就啥也不干了
🌈当前,interrupt 方法,效果不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要结束,立即结束还是等会结束,都是代码来灵活控制的interrupt 只是通知,而不是“命令”
1️⃣无视要求
Thread t = new Thread(() ->
//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t
// isInterrupted 就是t对象自带的一个标志位
while (!Thread.currentThread().isInterrupted())
System.out.println("hello t");
try //============> run 方法(并发执行)
Thread.sleep(1000);
catch (InterruptedException e)
//e.printStackTrace();
//break;
);
2️⃣立即执行
Thread t = new Thread(() ->
//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t
// isInterrupted 就是t对象自带的一个标志位
while (!Thread.currentThread().isInterrupted())
System.out.println("hello t");
try //============> run 方法(并发执行)
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
break;
);
3️⃣等会执行
Thread t = new Thread(() ->
//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t
// isInterrupted 就是t对象自带的一个标志位
while (!Thread.currentThread().isInterrupted())
System.out.println("hello t");
try //============> run 方法(并发执行)
Thread.sleep(1000);
catch (InterruptedException e)
//e.printStackTrace();
try
Thread.sleep(2000);
catch (InterruptedException ex)
ex.printStackTrace();
//注意!!! 一个非常重要的问题!!! 当 sleep 被唤醒的时候,sleep会自动的把 isInterrupted 标志位给清空 (true -> false)
break;
//如果需要结束循环,需要在catch中加入break
//这里只有第一次抛异常,第二次就不会抛异常:
//首先 isInterrupted 的初始标志位为 false======> 如果sleep执行的时候看到这个标志位为 false, sleep 正常进行休眠操作
//t.interrupt();】 如果当前标志位为 true ,sleep 无论是刚刚执行完是已经执行了一半,都会触发两件事
//1.立即抛出异常 2.清空标志位为 false
//为什么 sleep 要清空标志位呢?
//目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制
);
📗3.等待一个线程-join()
✨线程之间是并发执行的,操作系统对于线程的调度,是无序的,无法判定两个线程谁先执行结束,谁后执行结束
public static void main(String[] args)
Thread t = new Thread(() ->
System.out.println("hello t");
);
t.start();
System.out.println("hello main");
此时先执行 hello t 还是先执行 hello main 是无法知道的,并发执行的
这个代码实际执行的时候,大部分情况下都是先执行 hello main (因为线程创建也有开销),但是不排除特定情况下,主线程 hello main 没有立即执行到
👉有的时候就需要明确线程的结束顺序,可以使用 “线程等待” 来实现 ———— join 方法
public static void main(String[] args) throws InterruptedException
Thread t = new Thread(() ->
System.out.println("hello t");
);
t.start();
t.join();
System.out.println("hello main");
🔎是在 main 线程中,调用 t.join ,意思就是让 mian 线程等待 t 先结束,再往下执行;别的线程不受影响
(如果是 t1 线程中,调用 t2.join, 就是让 t1 线程等待 t2 先结束)在 t.join 执行的时候,如果 t 线程还没结束呢,main 线程就会 “阻塞”(Blocking) 等待。即代码走到这一行就停下来,当前这个线程暂时不参与 cpu 的调度执行了
✅t.join();
1) main 线程调用 t.join 的时候,如果 t 还在运行,此时 main 线程阻塞,直到 t 执行完毕(t 的 run 执行完了),mian 才从阻塞中解除,才能继续执行
2) main 线程调用 t.join 的时候,如果 t 已经结束了,此时 join 不会阻塞,就会立即往下执行
📘4.获取当前线程引用
public static Thread currentThread(); 返回当前线程对象的引用
public class ThreadDemo
public static void main(String[] args)
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
📙5.休眠当前线程
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠
public class ThreadDemo
public static void main(String[] args) throws InterruptedException
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
📒6.线程的状态
✨操作系统里的线程,自身是有一个状态的,但是 Java Thread 是对系统线程的封装,把这里的状态又进一步的精细化了
1️⃣NEW ———系统中的线程还没创建出来呢,只是有个 Thread 对象
public class ThreadDemo4
public static void main(String[] args)
Thread t = new Thread(() ->
System.out.println("hello");
);
//在启动之前,获取线程状态————NEW
System.out.println(t.getState());//NEW
t.start();
2️⃣TERMINATED——系统中的线程已经执行完了, Thread 对象还在
public class ThreadDemo4
public static void main(String[] args) throws InterruptedException
Thread t = new Thread(() ->
System.out.println("hello");
);
//在启动之前,获取线程状态————NEW
System.out.println(t.getState());//NEW
t.start();
Thread.sleep(2000);
System.out.println(t.getState());//TERMINATED
3️⃣RUNNABLE——就绪状态(1.正在CPU上运行 2.准备好随时可以去CPU上运行)
public class ThreadDemo5
public static void main(String[] args) throws InterruptedException
Thread t = new Thread(() ->
while (true)
//为了防止 hello 把线程状态冲没了,先注释掉
//System.out.println("hello");
);
//在启动之前,获取线程状态————NEW
System.out.println(t.getState());//NEW
t.start();
Thread.sleep(2000);
System.out.println(t.getState());//RUNNABLE
4️⃣TIMED_WAITING———指定时间等待,sleep 方法
public static void main(String[] args) throws InterruptedException
Thread t = new Thread(() ->
while (true)
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
);
//在启动之前,获取线程状态————NEW
System.out.println(t.getState());//NEW
t.start();
Thread.sleep(2000);
System.out.println(t.getState());//TIMED_WAITING
5️⃣BLOCKED———表示等待锁出现的状态
6️⃣WAITING———使用 wait 方法出现的状态
java线程之Thread类的基本用法
Thread类的基本用法
小鱼在上一篇博客详细的讲解了如何创建线程,java使用Thread类来创建多线程,但是对于好多没有相关经验的人来说,比较不容易理解的地方在于操作系统调度的执行过程. 我们通过下面代码举例:
public static void main(String[] args)
Thread t = new Thread(() ->
System.out.println("hello t");
);
//调用start(),创建一个新的线程
t.start();
System.out.println("hello main");
我们的java在启动时,会创建一个主线程,此时主线程的名字就是main,接下来我们执行main线程里面的一条条代码了,当我们执行到t.start()时,系统就会自动帮我们创建一个新的线程,并且会自动帮我们调用该线程中的run()方法,此时的run()方法就可以认为是个入口,一些重要的实现逻辑将都在里面实现.
我们通过上述代码举例:
当我们实例一个thread并且调用start()方法之后, main 线程和 t 线程就是(并发+并行)的过程,由于多线程有一个万恶之源–抢占机制,所以就会导致我们无法预测到程序是优先执行"hello t" 还是 “hello main”,并且对于谁先结束线程的任务,我们也是不确定的,可能是main线程优先结束,也可能是t线程优先结束.
创建线程是看start的顺序,但是具体的线程对应的任务什么时候执行,要看系统调度器。
1. Thread类的构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
关于构造方法的讲解在这篇文章中详细的讲到了,包括如何创建一个线程,以及创建线程需要注意的事项都在这里一一列举了出来多线程的创建及其注意事项
2. Thread的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
常见属性
(1) getid() : ID表示线程的身份.就类似于我们的身份证一样,是独一无二的.这里的方法获取到的是线程在JVM中的身份标识,线程的标识有好多个,在内核的PCb上有标识,在用户态线程数据库中也有标识(操作系统的线程库)
(2) getName() : 获取当前线程的名字,多用于调试时使用.
(3) getState() :获取当前线程的状态,状态表示线程当前所属的一个情况.
(4) getPriority() : 优先级高的线程理论上来说更容易被调度到,但实际上并没有什么区别.大概可以理解为,你建议我不要晚睡,但也仅仅是建议,我可以不听.
(5) isDaemon() :daemon称为"守护线程",也可以称为后台线程,如果是后台线程就会返回true
,前台线程则是false
,我们可以这么理解后台线程,当我们手机打开qq这个app时,此时qq就是前台运行,但是由于我们后续可能会切换到快手app,此时qq就来到后台运行了.
一个线程创建出现默认是前台线程,前台线程会阻止进程的退出,进程只有在所有的前台线程都执行完之后才可以退出,但是对于后台进程,并不会阻止进程的结束.
JVM会在一个进程的所有非后台进程结束后,才会结束运行。
(6)isAlive() : 是否存活,即run()方法是否结束。
public class Demo4
public static void main(String[] args)
Thread t = new Thread(new Runnable()
@Override
public void run()
System.out.println("t 线程正在运行");
);
System.out.println("t线程是否存活 "+t.isAlive());
t.start();
System.out.println("t线程是否存活 "+t.isAlive());
try
Thread.sleep(1000);
System.out.println("t线程是否存活 "+t.isAlive());
catch (InterruptedException e)
e.printStackTrace();
如果 t 的run还没跑,isAlive就为false;
如果 t 的run正在跑,isAlive就为true;
如果 t 的run跑完了,isAlive就为false;
执行结果:
此时我们将上述涉及到的属性的状态打印出来:
使用setDaemon(布尔值)方法设置一个线程为后台,true表示后台线程。
class MyThread1 extends Thread
@Override
public void run()
System.out.println("hello t");
public class Demo1
public static void main(String[] args)
Thread t = new MyThread1();
t.start();
System.out.println("hello main");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//设置为后台线程
t.setDaemon(true);
System.out.println("是否是后台线程 "+ t.isDaemon());
执行结果:
上述操作都是获取到的一瞬间的状态,不是持续的状态。
start()和run()方法的区别:
当我们调用start()这个方法时,创建一个新的线程(有一个向系统创建一个线程的这么一个动作),此时这个线程会由系统自动调用run()方法(入口方法).并且这个run()方法是在这个新建的线程中运行的.
但是如果是程序员手动调用run()方法的话,并不会新建一个线程,并且该方法是在调用这个方法的线程中执行的.
获取当前线程的引用:
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
public static void main(String[] args)
//获取当前线程的引用
Thread t = Thread.currentThread();
//通过当前线程的引用调用当前线程的名字
System.out.println("当前线程名字 : "+t.getName());
执行结果:
休眠当前线程:
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis毫秒 |
public static void sleep(long millis, int nanos) throwsInterruptedException | 可以更高精度的休眠 |
public static void main(String[] args)
Thread t = new Thread(()->
System.out.println("hello t");
);
t.start();
try
//睡眠1000ms,也可以称为阻塞1000ms
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("hello main");
睡眠的状态就相当于这个线程被阻塞了,阻塞的时间由里面的参数决定,在这个线程阻塞的时间并不影响其他线程执行,可以理解为~我们在打游戏,打着打着未成年时间限制弹了出来,这时候就要求我们停止打游戏这个行为,只有等这个等待时间过去之后才可以继续打游戏.
注:等待的过程中什么都不能做,相当于被定身了一样!!!
线程中断
中断线程,就是让线程尽快的把入口方法(run方法)执行结束。有2种方法。
(1) 直接手动创建标志位来区分线程是否要结束.
public class ThreadDemo5
static boolean flog = true;
public static void main(String[] args)
Thread t = new Thread(()->
while (flog)
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("线程运行中~~");
System.out.println("t 线程结束");
);
t.start();
try
//睡眠3000ms
Thread.sleep(3000);
//将标志位设置为false终止上面的while()
System.out.println("控制新线程结束");
flog = false;
catch (InterruptedException e)
e.printStackTrace();
运行结果:
有些线程可能会存在一些循环需要执行的情况,但是可能只是需要执行一定时间,并不是一直执行,所以就可以通过sleep()来控制这个线程结束的时间.
需要注意的是:flog需要是成员变量,局部变量是线程私有的,其余线程不能进行修改,但是成员变量是处于进程的数据区,是共享的资源,所以可以访问和修改~~
Thread内置了标志位,不需要手动创建标志位。
(2)Thread内置的标志位
public class ThreadDemo5
static boolean flog = true;
public static void main(String[] args)
Thread t = new Thread(()->
while (!Thread.currentThread().isInterrupted())
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("线程运行中~~");
System.out.println("t 线程结束");
);
t.start();
try
//睡眠3000ms
Thread.sleep(3000);
//将标志位设置为false终止上面的while()
System.out.println("控制新线程结束");
t.interrupt();
catch (InterruptedException e)
e.printStackTrace();
Thread.currentThread().这个是Thread类的静态方法,通过这个方法可以获取当前线程,类似this;
isInterrupted()方法是一个判定内置的标志位,默认是false,true表示线程要中断。现在运行,查看结果。
为什么在抛出异常后线程还是在不停的执行呢?
那是因为当代码执行到interrupt()时会做两件事:
(1) 如果t 线程没有处于阻塞状态时,这时候interrupt()就会修改内置标志位,将isInterrupted的状态设置为true;
(2) 如果 t 线程处于阻塞状态时,此时interrupt()
会通过触发异常,将阻塞状态(sleep)提前唤醒,但是由于把sleep唤醒会导致标志位变成false,也就是将标志位清空,如果标志位本身就是false则不变.由于我们的循环条件是while(!Thread.currentThread().isInterrupted())
,又因为我们的标志位又变成了false,所以我们可以继续执行这个循环.
实际中能产生阻塞的方法有很多,并不是只有sleep,这里只是用sleep举例子.
由于计算机的执行速度时很快的,所以阻塞状态大概占线程执行状态的99.99%,所以会有更大的几率触发(2).
当然,即使是大部分时间都在阻塞状态,但是该线程始终是在阻塞状态和正常运行的状态之间切换.
当然,我们可以通过break关键字,当抛出异常时直接break就可以不再执行循环,之后就可以结束线程,这就相当于我正在打游戏,我妈喊我吃饭,此时我不敢抗拒,只能去吃饭.
代码如下:
public class ThreadDemo5
public static void main(String[] args)
Thread t = new Thread(()->
while (!Thread.currentThread().isInterrupted())
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
break;
System.out.println("线程运行中~~");
System.out.println("t 线程结束");
);
t.start();
try
//睡眠3000ms
Thread.sleep(3000);
//将标志位设置为false终止上面的while()
System.out.println("控制新线程结束");
t.interrupt();
catch (InterruptedException e)
e.printStackTrace();
此时的运行结果:
当然,由于我们捕捉到了异常,我们不仅可以直接让他break,还可以让他先等待一会再结束执行.
就像我们在打电话一样:
比如我正在打游戏,女朋友让我出去买菜,此时我不搭理她,这个时候就相当于,你说你的我干我的.
但是呢?过了一会女朋友又来跟我说让我买菜去,我因为怕她生气嘛,所以我跟她说,五分钟,五分钟就好…等五分钟一到,我就退出游戏买菜去了.
当我买完菜的时候,刚躺到床上打开游戏,此时厨房传来女朋友的尖叫声,那我必须立刻放下手机去看看发生了什么事情…
总结三种状态;(1)立即退出 (2) 等待一会再退出 (3) 不退出,你说你的我做我的.
由我们自己来决定什么时候退出.
如果是直接写死的话,让我收到命令就必须退出,假如我在跟老板汇报工作,此时女朋友让我去买菜,如果我把老板电话挂掉,那自己也就寄了.
只有自己才有资格决定自己要做什么事情,别人的话之能当作意见,只有自己是为自己好.
等待一个线程
线程是一个随机调度过程,等待线程,就是控制两个线程的结束顺序.
但是呢?我们可以通过过join()
方法来控制线程的执行顺序.
大家看下面的代码以及执行结果:
由于线程是抢占式执行的,所以可能这次是先打印 main 但是下次可能就先打印t了,可是程序员不喜欢这种不确定性,所以我们可以通过join()
方法来控制顺序.
大家看下面的代码和执行结果:
当我们在 main 线程中调用 t.join()
,此时只有当 t 线程执行完之后,才会去执行 main 线程中的内容,我们也可以理解为,在哪个线程中调用join()
方法,那么就哪个线程阻塞,只有当调用这个方法的引用所对应的线程执行完之后,才可以继续执行后续代码~
当然,这个join()
也是可以传参数的,假如 t 线程一直没有执行完,那么我这个线程就不工作了? 就像我约女神见面一样,我等了一天,两天,三天都没等到,难道我还要等一辈子? 所以此时我们就可以传一个参数,作为最大等待时间,超过这个时间我就不等了,世界上美女那么多,我换个人舔还不行?
join方法自身也会阻塞,Java线程但凡是阻塞的方法都可能抛出这个异常(throws InterruptedException
)。
join有两种行为:
(1)如果被等待的线程还没有执行结束,那么直接阻塞等待;
例如:我接我孩子放学,他5.00放学,我4.50到了,此时我就需要等待他放学,菜才能回家.
(2)如果被等待的线程已经执行结束了,就直接返回。
例如:我接我孩子放学,他5.00放学,我5.10才到,那么我就不用等,可以直接接回家.
join方法不带参数的时候,就是死等,一直等待下去。方法带参数的时候,如果超过了等待的最大时间,就开始执行下一步的代码。一般写带参数的join方法。
以上是关于Thread 类的基本用法JavaEE的主要内容,如果未能解决你的问题,请参考以下文章