多线程的创建方式以及及Thread类详解
目录
一.线程的创建方法:(重点)
一:继承Thread类
写法一:正常写法
写法二:匿名内部类
二.实现Runnable接口
写法一:正常写法
写法二:匿名内部类
三. 实现 Callable 接口
编辑
四.通过线程池创建线程
二.Thread类及常⻅⽅法
1.Thread的常⻅构造⽅法
2.Thread的⼏个常⻅属性
3.Thread常见方法(重点)
run()方法 和start()方法(区别)
join()方法
中断线程
sleep()方法
一.线程的创建方法:(重点)
一:继承Thread类
写法一:正常写法
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务
创建Thread子类的实例,即创建了线程对象
调用线程对象的start()方法来启动该线程
public class MyThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("这里是子线程");
}
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.start();
while (true) {
System.out.println("这里是主线程");
}
}
}
写法二:匿名内部类
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("这里是thread线程");
}
}
};
thread.start();
while (true) {
System.out.println("这里是main线程");
}
}
二.实现Runnable接口
写法一:正常写法
- 创建一个实现了Runnable接口的类,并实现接口中的run()方法,定义线程的执行逻辑。
- 在主线程中创建Runnable实例,并将其作为参数传递给Thread类的构造方法。
- 调用Thread对象的start()方法启动线程。
public class MyRunnable implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("thread!");
}
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
while (true) {
System.out.println("main!");
}
}
}
写法二:匿名内部类
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("thread!");
}
}
});
thread.start();
while (true) {
System.out.println("main!");
}
}
三. 实现 Callable 接口
前两种方式都存在一个问题:重写的run方法均不能直接返回结果,不适合需要返回线程执行结果的场景。而通过实现Callable接口则可以做到这一点。
- 得到任务对象定义类实现Callable接口,重写call方法,封装要做的事情,用FutureTask把Callable对象封装成线程任务对象
- 把线程任务对象交给Thread处理
- 调用Thread的start方法启动线程,执行任务
- 线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 3; i++) {
System.out.println("thread:" + i);
}
return "call!";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
for (int i = 0; i < 3; i++) {
System.out.println("main:"+i);
}
System.out.println(futureTask.get());
}
}
四.通过线程池创建线程
后序再详讲
二.Thread类及常⻅⽅法
1.Thread的常⻅构造⽅法
- Thread t1 = new Thread();
- Thread t2 = new Thread(new MyRunnable());
- Thread t3 = new Thread("这是我的名字");
- Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
2.Thread的⼏个常⻅属性
- ID是线程的唯⼀标识,不同线程不会重复
- 名称是各种调试⼯具⽤到
- 状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明
- 优先级⾼的线程理论上来说更容易被调度到
- 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
- 是否存活,即简单的理解,为run⽅法是否运⾏结束了
- 线程的中断问题,下⾯我们进⼀步说明
3.Thread常见方法(重点)
run()方法 和start()方法(区别)
- start()方法是Thread类中的一个方法,用于启动一个新的线程。当调用start()方法时,系统会创建一个新的线程,并在新的线程中执行run()方法的内容。start()方法会在新的线程中执行一些准备工作,然后调用run()方法。
- run()方法是实现了Runnable接口的类中的一个方法。在启动一个线程后,系统会自动调用该线程对象的run()方法。run()方法中包含了线程的主体代码,即线程要执行的任务。
2.start()方法
public class MyThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("这里是thread");
}
}
public static void main(String[] args) {
MyThread thread = new MyThread() ;
thread.start();
while (true) {
System.out.println("这里是main线程");
}
}
}
此时为俩个线程并发处理
1.run()方法
public class MyThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("这里是thread");
}
}
public static void main(String[] args) {
MyThread thread = new MyThread() ;
thread.run();
while (true) {
System.out.println("这里是main线程");
}
}
}
此时只实现了run方法 并没有并发处理
总结:
start()
方法 用于启动一个新线程,它会触发线程的创建和调度,最终由操作系统负责执行run()
方法。run()
方法 是线程的实际执行任务,是线程执行的代码部分。如果直接调用run()
方法,它会在当前线程中执行,并不会启动新的线程。
join()方法
在多个线程中,他们的执行顺序属于系统调动的,是无序的(抢占式执行)
而我们希望一个稳定的顺序,如何执行?
join()
方法的使用场景:场景 1:确保多个线程按顺序执行
有时,你可能希望在主线程中启动多个子线程,并确保在继续执行后续任务之前,所有子线程已经完成。例如,在处理多个任务并且它们之间有依赖关系时,必须等待所有线程完成。
场景 2:等待线程完成后汇总结果
在多线程并发计算时,主线程可能需要等待多个线程完成任务,才能进行汇总或处理结果。
join()
方法可以帮助你等待所有线程执行完成后再进行下一步操作。场景 3:避免主线程提前退出
如果没有使用
join()
,主线程可能会在子线程完成之前就退出,导致子线程未执行完成。使用join()
可以保证主线程在所有子线程完成之前不会退出。
案例:
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
public void run() {
System.out.println(name + " 正在执行...");
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " 执行完毕!");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
MyThread thread3 = new MyThread("线程3");
thread1.start();
thread2.start();
thread3.start();
// 主线程等待所有子线程执行完毕
thread1.join();
thread2.join();
thread3.join();
System.out.println("所有线程都已执行完毕,主线程继续执行...");
}
}
- 在这个例子中,主线程通过调用
thread1.join()
,thread2.join()
和thread3.join()
来等待所有子线程执行完毕。- 主线程会阻塞,直到
thread1
、thread2
和thread3
执行完成。- 当所有线程执行完毕后,主线程输出
"所有线程都已执行完毕,主线程继续执行..."
。
总结:
确保线程顺序:有时我们希望线程按照特定的顺序执行。使用
join()
可以让主线程等待子线程执行完毕,这样就能保证主线程在子线程之后才继续执行。避免主线程提前退出:在多线程应用中,如果主线程没有等待子线程完成就退出,可能导致子线程未完成时程序就结束了,特别是在没有使用
join()
时,主线程可能比子线程先结束。汇总线程结果:如果多个线程在并行执行,主线程可能需要等待它们执行完毕后再进行结果汇总。
join()
方法能够确保线程完成后,主线程能够获取到所有线程的执行结果。避免线程竞态条件:通过确保线程按顺序执行,
join()
可以避免一些可能的竞态条件和线程同步问题。
中断线程
在 Java 多线程编程中,中断线程是一种用于请求线程停止执行的机制。线程的中断并不意味着直接停止线程的执行,而是给线程发送一个中断信号,让线程有机会在合适的地方处理这个信号并终止。线程中断通常用于协调多个线程的生命周期,特别是在需要停止一个线程的执行时,或者是某个线程的操作不再需要时。
线程中断的使用场景:
停止正在执行的线程: 当线程正在进行耗时的任务时,如果你需要在某个时刻停止它,可以通过中断来实现。中断信号告诉线程它可以停止工作或清理资源并退出。
应用场景:比如有一个后台线程在执行某些长时间的计算任务或下载操作,如果用户取消了操作,你可以通过中断线程来停止这些任务。
响应外部中断请求: 比如在一个多线程的客户端应用中,如果用户要求退出程序或者停止某些操作,可以通过发送中断信号来优雅地停止正在运行的线程。
应用场景:比如在一个多线程的 HTTP 请求处理系统中,如果服务器需要停止处理请求,可以通过中断正在处理请求的线程。
在阻塞操作中响应中断: 当线程执行如
sleep()
、join()
、wait()
等阻塞操作时,可以中断线程来提前终止阻塞操作,从而使线程能够继续执行其他任务。应用场景:在多线程的网络编程中,可能需要等待其他线程的响应,如果等待超时,则可以通过中断等待线程,防止程序长时间处于等待状态。
取消正在执行的任务: 在一些任务执行框架中,可以通过中断来取消正在执行的任务。当任务执行时间过长,或者任务本身不再需要时,可以通过中断来提示线程终止。
应用场景:比如在处理大量数据时,当用户主动取消任务时,可以中断相关的线程。
案例1:使用 Thread.interrupt()
中断线程
public class InterruptExample {
public static void main(String[] args) {
// 创建线程
Thread thread = new Thread(() -> {
try {
System.out.println("线程开始执行");
// 模拟一个长时间的任务
for (int i = 0; i < 10; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("线程被中断,退出循环");
return;
}
Thread.sleep(1000); // 模拟工作
System.out.println("线程正在工作,执行第 " + (i + 1) + " 次");
}
} catch (InterruptedException e) {
System.out.println("线程在阻塞时被中断:" + e.getMessage());
}
});
thread.start();
try {
// 等待一会儿,然后中断线程
Thread.sleep(3000);
thread.interrupt(); // 请求中断线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
示例 2:线程阻塞时的中断
public class InterruptInSleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
System.out.println("线程开始执行,准备休眠");
Thread.sleep(5000); // 模拟长时间的阻塞操作
System.out.println("线程正常执行完毕");
} catch (InterruptedException e) {
System.out.println("线程被中断,异常信息:" + e.getMessage());
}
});
thread.start();
try {
// 等待 2 秒钟然后中断线程
Thread.sleep(2000);
thread.interrupt(); // 请求中断线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
总结
- 中断线程是通过
Thread.interrupt()
方法发出请求,线程可以通过检查中断状态来响应这个请求。- 常见的使用场景包括:停止长时间运行的任务、在阻塞操作中响应外部请求、取消正在进行的操作等。
- 线程中断是一种“协作式的停止”,即线程需要在合适的地方主动检查中断状态并退出。
sleep()方法
在Java中,sleep()
方法是 Thread
类中的一个静态方法,用于暂停当前执行的线程指定的时间
案例:定时打印信息
public class TimerTaskExample {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
for (int i = 1; i <= 5; i++) {
System.out.println("第 " + i + " 次执行");
try {
// 每次执行后休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Long end = System.currentTimeMillis();
System.out.println("总共耗时:"+(end-start));
}
}
结语: 写博客不仅仅是为了分享学习经历,同时这也有利于我巩固知识点,总结该知识点,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进。同时也希望读者们不吝啬你们的点赞+收藏+关注,你们的鼓励是我创作的最大动力!