Java EE - Thread类的基本使用
目录
- 1.Thread类的基本介绍
- 2.创建线程
- 2.1 自定义线程类MyThread
- 2.2 自定义一个MyRunable类
- 2.3 Thread 和 Runnable
- 2.4 lambda表达式
- 3. sleep方法
- 4.currentThread方法
- 5.线程的终止
- 6.小结
1.Thread类的基本介绍
Thread类是Java标准库中提供用来管理线程的类,里面封装操作系统对线程管理方面的API,Java中创建的每一个线程,可以理解为Thread类的实例化对象,用来描述线程信息,可以视为Thread类是对操作系统提供线程管理的API进一步的抽象和分离。
2.创建线程
2.1 自定义线程类MyThread
定义一个MyThread类,继承于Thread类,Thread中有一个run方法,需要在子类中重写,MyThread类重写完成后,实例化MyThread的对象,就完成线程的创建,但是线程的真正的调用需要使用Thread类或者子类中的start方法,才会启动线程,在创建线程后就可以调用start方法。
//自定义一个MyThread类继承于Thread类,出现Thread类中的方法
class MyThread extends Thread{@Overridepublic void run(){System.out.println("run方法已经启动。");}
}
public class Main{public static void main(String[] args){//实例化一个线程类MyThread myThread = new MyThread();//启动线程myThread.start();}
}

2.2 自定义一个MyRunable类
MyRunnable类使用Runnable接口,重写接口中的run方法,但是线程的创建需要使用到Thread类,因此还需要创建一个Thread类,将MyRunnable实例化对象作为Thread类构造方法的参数。
在Thread类中提供一个参数为Runnnable接口引用的构造方法,可以将引用传入。
//Thread类的构造方法public Thread(Runnable target) {this(null, target, "Thread-" + nextThreadNum(), 0);}
class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println("MyRunnable: run方法已经启动。");}public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread t = new Thread(myRunnable);}
}

2.3 Thread 和 Runnable
Thread类是Java标准库提供用来管理线程的类,但是内部并没有具体实现run方法,而是调用Runnable接口中的run方法,Runnable是一个接口,内部也没有实现run方法,所以需要创建Thread的子类或者将使用Rnnbale接口的类的对象作为Thread构造方法的参数。
//Thread类的run方法@Overridepublic void run() {if (target != null) {target.run();//进入run方法,实际使用Runnable的run方法}}
@FunctionalInterface
//Runnable的run方法
public interface Runnable {public abstract void run();
}
Thread的子类可以使用匿名内部类的方式创建,在子类中重写run方法。
//匿名内部类创建
class out{public static void main(String[] args) {//使用Thread作为匿名内部类的父类Thread thread = new Thread(){//重写run方法,实际调用的是重写的run@Overridepublic void run(){System.out.println("out: run方法启动");}};//启动线程thread.start();}
}

Runnable的子类使用匿名内部类的方式创建,使用Runnable接收实例化对象,再将对象引用传入Thread的构造方法中。
class Rout{public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("Runnable: run方法启动");}};Thread thread = new Thread(runnable);//启动线程thread.start();}
}

2.4 lambda表达式
lambda表达式创建的匿名内部类的对象引用作为Thread类构造方法的参数传入。
class Lambda{public static void main(String[] args) {Thread thread = new Thread(//lambda表达式创建的() -> {System.out.println("Lambda: run方法已启动");});//启动线程thread.start();}
}

3. sleep方法
sleep方法是让线程的执行进入等待,可以传入一个参数,单位是毫秒,例如sleep(1000)就是休眠等待一秒,sleep方法会让线程进入等待,这个方法默认是在线程中是不安全的,需要取捕获异常**InterruptedException**(每个让线程进入等待的方法都会捕获的异常),捕获异常的方法有两种,第一种是在sleep方法中加上try catch,第二种是在main方法中加上声明,最终由JVM处理异常。
例如 :重写的run方法中输出10次打印,每次间隔1000毫秒,记录调用sleep程序的执行的时间。
class Sleep{public static void main(String[] args) {Thread thread1 = new Thread(() ->{long start = System.currentTimeMillis();//开始for (int i = 0; i < 10; i++) {System.out.println("run方法打印中------");//捕获异常try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}};long end = System.currentTimeMillis();//接收System.out.println("时间:" + (end - start) + "ms");//毫秒});//启动线程thread1.start();}
}

4.currentThread方法
currentThread方法是Thread类中实现的静态方法,可以通过Thread类调用,currentThread方法可以获取当前线程执行的对象引用,使用该引用就可以执行Thread中的方法,如sleep,getName(获取线程名字)。
class Test{public static void main(String[] args) {Thread thread = new Thread(() -> {//记录sleep函数的执行时间long s = System.currentTimeMillis();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}long e = System.currentTimeMillis();System.out.println("sleep休眠时间:" + (e - s));//获取线程名System.out.println("获取当前线程名字:" + Thread.currentThread().getName());});thread.start();}
}

线程如果未命名,系统默认线程名是Thread-0,如果需要重命名可以在Thread的构造方法中多传入一个参数name,作为线程名字.
//Thread类中的构造方法,一个是Runnable对象引用,一个是线程名public Thread(Runnable target, String name) {this(null, target, name, 0);}
class Test{public static void main(String[] args) {Thread thread = new Thread(() -> {long s = System.currentTimeMillis();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}long e = System.currentTimeMillis();System.out.println("sleep休眠时间:" + (e - s));System.out.println("获取当前线程名字:" + Thread.currentThread().getName());},"线程1");//添加线程名字thread.start();}
}

5.线程的终止
线程的终止有两种方法:
1.第一种是使用标识符,定义一个变量flag,通过改变flag使线程提前终止
例如:定义一个布尔类型的变量flag,初始默认为false,当flag更改为true时,停止线程。
class T{//定义标识符static boolean flag = false;public static void main(String[] args) {Thread thread = new Thread(() -> {//标识符控制while(!flag){System.out.println("线程最执行中----");System.out.println("输入1停止线程,输入其它继续线程");System.out.print("请输入:");Scanner in = new Scanner(System.in);if(in.nextInt() == 1) flag = true;//改变标识符终止线程}System.out.println("线程结束");});thread.start();}
}

如果此时将标识符设置在mian函数内,是否可以正确执行呢?

可以看到将标识符在main方法定义,程序就会报错,错误是因为lambda表达式内部变量的使用会触发“变量捕获” 的机制,被捕获的局部变量需要满足是被final修饰的,或者实际上是final的(不被修改的),当flag是可被修改的就会在编译时报错。
多个线程的执行是并发执行的,例如在上述出现中就有main线程(主线程)和Thread-0线程,在Thread线程开启的同时,main线程也会开启,并发执行,在lambda中使用的变量是一份临时的拷贝,并不是原有的变量,main线程结束时,捕获的变量也可以继续使用,不会因线程的结束而无法使用,但是如果捕获的变量是可修改的,main线程可能修改后,还未等到lambda表达式去拷贝修改后的变量就结束线程,导致捕获的结果错误,所以lambda表达式中的局部变量必须是final或者实际上是final的。

为什么在类中定义的静态变量不会报错?
类中的静态变量在类加载的时候存放于静态区,访问可以通过类或者成员方法访问,类成员变量的销毁伴随类的销毁,而线程的结束通常是比类的销毁更快(类的销毁涉及GC垃圾回收的复杂机制),所以在类中定义的成员变量在lambda表达式中不会出现报错信息。
2.线程终止的第二种方法是调用interrupt方法,提前终止线程,interrupt方法通常搭配isInterruoted方法使用,isInterrupted方法可以认位是调用标识符,interrupt方法是将标识符改变,终止线程。
//isInterrupted方法默认调用标识符,interrupted = falseprivate volatile boolean interrupted;
class Interrupt{public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {//判断是否终止线程while(!Thread.currentThread().isInterrupted()){System.out.println("线程执行中.....");try {Thread.sleep(1000);} catch (InterruptedException e) {//未输出异常原因}}});//开启线程thread.start();//确保调用interrupt方法线程开始执行Thread.sleep(1);//终止线程thread.interrupt();}
}

程序执行后会一直打印线程执行中,并没有终止线程,这是因为在调用interrupt方法的时候会先把isInterrupted方法获取的标识符改为true,但是interrupt方法会重新唤醒sleep方法,又将标识符改为false,导致线程继续执行。
可以在捕获异常后执行输出异常的原因,终止线程。
try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);//抛出异常,终止进程}

在catch中直接加入break,可以终止线程,不会立马终止进程。
try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("线程结束");break;}

6.小结
本篇内容涵盖了Java EE中Thread类的基本使用,包括线程创建、休眠、获取当前线程以及线程终止等核心操作。掌握这些基础知识为理解多线程编程的状态管理、线程安全及设计模式应用奠定基础。后续内容将深入探讨线程的状态转换及相关技术细节,进一步扩展Java EE多线程编程的实践能力。
期待下次继续带你深入Java EE的更多技术细节,下期见!
