本文让我们一起来认识Thread的基本用法,包括如何创建线程,中断线程,线程等待,线程休眠,获取线程实例等方法
目录
观察并体会多线程代码执行
线程创建
线程中断
方案一:手动创建一个结束标志位
方案二:使用线程自带的标志位
线程等待
线程休眠
获取线程实例
观察并体会多线程代码执行
public class ThreadDemo4 {
public static void main(String[] args) {
//这里创建了一个t线程,线程代码是死循环打印 hello world
Thread t = new Thread(()->{
while(true){
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//新线程入口
t.start();
//主线程死循环打印hello t
while(true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面代码,其中一个为我们执行代码的主线程(main线程),另一个则是我们自己创建的 t 线程 ,t 线程死循环打印 hello word ,主线程则死循环打印 hello world 。
在没学线程之前,我们按照普通调用方法来看,按理说不会打印 hello t ,因为 在进入t 线程里的代码已经死循环了,那么结果真的是这样吗?
下面给出结果
此时是hello t 和 hello world 交替打印,为什么呢?
因为这里是两个线程,这两个线程是同时运行的,两个运行顺序并不相干。
当我们运行程序的时候 主线程在运行,当代码执行到start开始执行 t 线程时,此时两个线程同时执行。
此时我们就能很直观的观察到多线程的执行状态了
线程创建
创建线程主要是创建一个线程类(依靠Thread来创建),然后得到一个线程的实例对象,其中需要注意的是需要重写Thread中的特殊的方法(run方法),run方法可以看着新线程的main方法,由线程入口start方法来开启线程,此时会创建一个新线程并且执行
线程的创建大致有四种方法
1. 创建一个继承Thread的类,类中重写run方法,再进行实例化对象。
2.通过创建类实现Runnable接口,类中重写run方法,再进行实例化对象
3.通过Thread匿名内部类实现,类中重写run方法
4.通过lambda表达式实现
其中使用最广泛的是使用lambda表达式来创建一个基于Thread的匿名内部类的实例,代码如下
public class Test{
public static void main(String[] args){
//此处用lambda表达式创建 语法()->{} {}里面是执行的run语句
Thread t = new Thread(() -> {
//此处写需要实现的代码,我们想要执行新线程的代码
System.out.println("hello world");
});
//调用start来执行新线程
t.start();
}
}
此处结果则是打印hello world,那么此时这里就有两个线程了,其中一个为我们执行代码的主线程(main线程),另一个则是我们自己创建的 t 线程 ,且此时打印的hello world 是 t 线程代码执行产生的结果。
能在main方法里直接调用run方法吗?
可以调用,虽然此时结果仍然是打印 hello world
但是这里有一个很重要区分,如果我们直接调用run方法,而不是通过start方法(线程入口方法),那么此时就是一个普通的调用方法,打印的hello world 是主线程作用的结果,并不会创建一个新线程执行代码,
线程中断
有时我们创建的线程,线程在执行时,有时需要中断线程,让线程下来停下来,那么此时我们该怎么操作呢
方案一:手动创建一个结束标志位
public class Test{
//此处手动设置一个结束标志位 ct
public static boolean ct = false;
public static void main(String[] args) {
Thread t = new Thread(()->{
while(!ct){
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//把标志位置位true,上面的循环就结束了,线程也就执行完了
ct = true;
}
}
此时我们手动设置了一个 ct 的成员变量为结束标志位,直接给定为false(在lambda中进行变量捕获,后面有解释),其中我们 t 线程是在死循环的,当把标志位ct 置为true,此时 t 线程从循环中退出,线程结束。
需注意:lambda表达式中如果需要引用到外界的变量,那么需要实现变量捕获
变量捕获规则:Java规定变量捕获时,捕获的变量必须是final修饰的变量,或者是本质上的final变量(不被final修饰,但是没有更改过的变量)
方案二:使用线程自带的标志位
通过isInterrupted方法获取,默认为false;
public class Test {
public static void main(String[] args) {
Thread t = new Thread(()->{
//Thread.currentThread()获取当前进程的引用
//通过自带的isInterrupted方法来获取标志位(默认为false)
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello world");
//情况二:终止的线程被堵塞(休眠等操作堵塞了线程)
//try {
// Thread.sleep(1000);
//} catch (InterruptedException e) {
// e.printStackTrace();
// break;//线程介绍的标志(得显式结束)
//}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过interrupt方法把标志为置为true
t.interrupt();
}
}
第一种情况:
通过调用 isInterrupted方法来获取一个标志位(默认为false),此时 t 线程开始执行,那么线程代码是死循环的,那么我们通过interrupt方法来把标志为转换为true,此时线程就结束了。
第二种情况:
如果通过interrupt方法来结束线程,当要结束的线程正在执行sleep等会堵塞线程的代码时(休眠线程),此时终止休眠,sleep会抛出一个异常,并且把标记位重置(默认为false),那么此时通过interrup方法把标记位置为true后,sleep有把标志为重置为默认状态了,那么进程也并不会停止结束,只是会单独的抛出一个异常。
那么此时需要结束线程,则需要捕获捕获异常,然后显式结束(同break或者return等结束操作)
如果没有显式结束,那么此时线程通过sleep等会堵塞线程的方法来抛出异常,并且继续执行。
线程等待
方法:join();
例如在main线程里,执行 t.join();
那么此时,main线程等待 t 线程执行,意思就是 :(此时main线程不再执行,等 t 线程执行结束,然后main线程再继续执行)
join方法有两种形态
第一种:不设置参数,则会进行死等待,必须等到 t 线程执行结束,main才会继续执行
第二种:join(参数),设置等待时长,单位毫秒,此时不会进行死等待,当等待时长到了,main线程不管 t 线程执没执行完都继续执行
t.join();
t.join(1000);
线程休眠
方法:sleep(参数)
通过Thread类来调用:Thread.sleep(参数)
线程执行到这句代码处,则该线程休眠,休眠时长根据参数而定,单位毫秒
注:调用sleep需要抛出或者捕获InterruptedException这个异常
Thread.sleep(1000);//此处线程休眠1000毫秒
获取线程实例
Thread Thread = Thread.currentThread();//获取当前线程的实例
通过Thread类调用static方法 currentThread()方法可以获取当前线程的实例,通过实例可以调用Thread类中的众多非静态的成员方法
本篇内容介绍到这里就差不多结束了,有其他知识点我们后面再作补充