本篇博客,我将为大家介绍Thread类的基本用法,包括线程创建、中断、等待、休眠和获取线程实例等方面,希望对大家有一些帮助和参考
目录
1.4 使用lambda 表达式创建 Runnable 子类对象
一、线程的创建
1.1 继承Thread的方式去创建线程
1) 继承 Thread 来创建一个线程类.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
2) 在main方法里创建 MyThread 类的实例
MyThread t = new MyThread();
3) 调用 start 方法启动线程
t.start(); // 线程开始运行
完整代码如下:
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("t");
}
public static void main(String[] args) {
MyThread t=new MyThread();
t.start();//启动线程
System.out.println("main");//主线程的打印
}
}
运行结果如下:
1.2 通过实现Runnable接口创建线程
1) 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
2) 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
3) 调用 start 方法
t.start(); // 线程开始运行
完整代码如下:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable());
t.start();
System.out.println("main");
}
}
运行结果如下:
1.3 使用匿名内部类创建线程
这里将介绍俩种实现方式
以下是第一种方式,就是继承thead的方式去实现
//使用匿名内部类实现继承thread
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run(){
while (true){
System.out.println("t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
while (true){
System.out.println("main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
另外一种是实现runable接口的方式去实现线程的创建
//匿名内部类实现ThreadRunable接口
public class MyThread {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
while (true){
System.out.println("main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.4 使用lambda 表达式创建 Runnable 子类对象
public class MyThread {
public static void main(String[] args) {
Thread t=new Thread(() ->{
while (true){
System.out.println("t");
}
});
t.start();
while (true) {
System.out.println("main");
}
}
使用lambda表达式是为了使代码看起来更加简洁,跟其他创建线程方法功能一样,除此之外,工作中使用的最多的也是这种
运行结果如下:
可能大家会好奇为什么不是依次交替执行的呢,本期疑问下期会给大家讲,先留一个悬念 :)
1.5 start方法和run方法的区别
下面来介绍一下,start方法和run方法的区别:
run() 方法只是线程的一个普通方法,调用它不会创建一个新的线程,而只是按顺序执行其中的代码。因此,如果直接调用 run() 方法,那么其中的代码会在当前线程中执行,不会开启新的线程。
start() 方法才是真正启动一个新线程并开始执行其中的 run() 方法。当调用 start() 方法时,Java 会创建一个新的线程,并在新的线程中执行 run() 方法中的代码。因此,如果要启动一个新线程并执行其中的代码,就必须使用 start() 方法。
二、线程中断
我们现来具体的描述一下 ,什么是线程中断,我们来描述一下:线程中断是指在多线程程序中,中断正在运行的线程的执行,简单来说就是让程序停下来,通常是由其他线程通过调用目标线程的interrupt()方法来实现。
我们来看下面一个例子:
李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们 需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如 何通知李四停止呢?这就涉及到我们的停止线程的方式了。
目前常见的有以下两种方式: 1. 通过共享的标记来进行沟通 2. 调用 interrupt() 方法来通知
案例1-使用自定义的变量来作为标志位.
需要给标志位上加 volatile 关键字,这个关键字的功能我们后面介绍
public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
target.isQuit = true;
}
}
案例2-使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
使用 thread 对象的 interrupted() 方法通知线程结束.
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
// 两种方法均可以
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()
+ ": 有内鬼,终止交易!");
// 注意此处的 break
break;
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
thread.interrupt();
}
}
thread 收到通知的方式有两种: 1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志
附:当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择 忽略这个异常, 也可以跳出循环结束线程.
2. 否则,只是内部的一个中断标志被设置,thread 可以通过Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
或者通过Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
案例3-观察标志位是否清除
使用 Thread.isInterrupted() , 线程中断会清除标志位.
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
thread.start();
thread.interrupt();
}
}
运行结果如下:
true // 只有一开始是 true,后边都是 false,因为标志位被清
false
false
false
false
false
false
false
false
false
使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
thread.start();
thread.interrupt();
}
}
运行结果如下:
true // 全部是 true,因为标志位没有被清
true
true
true
true
true
true
true
true
true
三、线程等待
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转 账成功,才决定是否存钱,这时我们可以使用join方法明确等待线程的结束。
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ": 我还在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我结束了!");
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始工作");
thread1.start();
thread1.join();
System.out.println("李四工作结束了,让王五开始工作");
thread2.start();
thread2.join();
System.out.println("王五工作结束了");
}
}
运行结果如下:
我们可以看到王五线程会等李四线程结束了再开始工作,工作中某些场景可能会涉及到
四、线程休眠
也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实 际休眠时间是大于等于参数设置的休眠时间的
方法 | 说明 |
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);//让此线程休眠3秒
System.out.println(System.currentTimeMillis());
}
}
上面的代码演示是让上面的线程休眠3秒
五、获取当前线程引用
方法 | 说明 |
public static Thread currentThread(); | 返回当前线程对象的引用 |
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();//获取当前线程
System.out.println(thread.getName());//获取当前线程的名字
}
}
本期内容就到这里,下期我会给大家分享更多内容