初识多线程
1. 认识线程(Thread)
1) 线程是什么
⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏着多份代码.
银行的例子:⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两 位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有 了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。此时,我们就把这种情况称为多线程, 将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队 执⾏ 。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。
2) 为啥要有线程
⾸先, "并发编程" 成为 "刚需".
• 单核 CPU 的发展遇到了瓶颈. 要想提⾼算⼒, 就需要多核 CPU. ⽽并发编程能更充分利⽤多核 CPU 资源.• 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程.其次, 虽然多进程也能实现 并发编程, 但是线程⽐进程更轻量.• 创建线程⽐创建进程更快.• 销毁线程⽐销毁进程更快.• 调度线程⽐调度进程更快.最后, 线程虽然⽐进程轻量, 但是⼈们还不满⾜, 于是⼜有了 "线程池"(ThreadPool) 和 "协程"(Coroutine)
3) 进程和线程的区别
• 进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程。
• 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.
⽐如之前的多进程例⼦中,每个客⼾来银⾏办理各⾃的业务,但他们之间的票据肯定是不想让别⼈知 道的,否则钱不就被其他⼈取⾛了么。⽽上⾯我们的公司业务中,张三、李四、王五虽然是不同的执 ⾏流,但因为办理的都是⼀家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最⼤区 别。
• 进程是 系统分配资源 的最⼩单位,线程是 系统调度 的最⼩单位。
• ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带⾛(整个进程崩溃).
4) Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对⽤⼾层提供了⼀些 API 供⽤⼾ ,使⽤(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进⾏了进⼀步的抽象和封装.
1.2 第⼀个多线程程序
感受多线程程序和普通程序的区别:
- 每个线程都是⼀个独⽴的执⾏流
- 多个线程之间是 "并发" 执⾏的.
2.实现多线程
1.通过继承Thread父类来实现多线程
class MyThread extends Thread{@Overridepublic void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Demo1 {public static void main(String[] args) throws InterruptedException {MyThread t = new MyThread();// 启动线程t.start();// 主线程循环打印while(true){System.out.println("hello main");Thread.sleep(1000);}}
}
关键点说明:
Thread.start()
vsThread.run()
:
start()
方法启动新线程并调用run()
方法。而直接调用run()
方法只会在当前线程中执行该方法的内容,并不会创建新线程。- 如果你调用
t.run()
,那么实际上还是在主线程中执行run()
方法的代码,而不是在新线程中执行。线程的并发执行:
- 主线程和子线程是并发执行的,打印的顺序是由操作系统的线程调度器决定的,可能会在不同的环境下表现出不同的输出顺序。
也可以通过匿名内部类来实现:
public class Demo3 {public static void main(String[] args) throws InterruptedException {// 创建线程的匿名内部类并实现 run() 方法Thread t = new Thread(){@Overridepublic void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000); // 每秒打印一次} catch (InterruptedException e) {e.printStackTrace();}}}};// 启动子线程t.start();// 主线程循环打印while(true){System.out.println("hello main");Thread.sleep(1000); // 每秒打印一次}}
}
匿名内部类:
Thread t = new Thread(){@Overridepublic void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}
- 你通过创建一个
Thread
类的匿名内部类来覆盖run()
方法。- 在
run()
方法中,线程执行的代码逻辑是一个无限循环,打印 "hello thread" 每秒一次。- 这种方式相较于继承
Thread
类的方式,避免了显式创建一个子类,可以让代码更简洁。
2.通过实现Runnable接口来实现多线程:
class MyRunnable implements Runnable {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000); // 让线程每秒睡眠一次} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Demo2 {public static void main(String[] args) throws InterruptedException {MyRunnable runnable = new MyRunnable();Thread t = new Thread(runnable); // 创建线程并传入任务t.start(); // 启动线程while (true) {System.out.println("hello main");Thread.sleep(1000); // 主线程每秒打印一次 "hello main"}}
}
为什么可以这样传参??
Thread t = new Thread(runnable); // 创建线程并传入任务
Runnable
接口public interface Runnable {
void run(); // 任务的执行代码
}
在 Java 中,
Runnable
是一个函数式接口,它有一个run()
方法。这个接口是用来表示某个可以由线程执行的任务的。具体来说,它代表了一个任务的“行为”,即一段可以并行执行的代码。一个实现了Runnable
接口的类就可以定义如何执行该任务。
2. Thread 构造函数接受 Runnable 对象
Thread
类有多个构造函数,其中一个构造函数是:public Thread(Runnable target)
使用匿名内部类:
public class Demo4 {public static void main(String[] args) throws InterruptedException {// 通过匿名内部类来实现 Runnable 接口Thread t = new Thread(new Runnable() {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});t.start(); // 启动线程// 主线程的任务while(true){System.out.println("hello main");Thread.sleep(1000); // 每秒输出一次 "hello main"}}
}
3.通过Lambda表达式来实现多线程:
public class Demo5 {public static void main(String[] args) throws InterruptedException {// 使用 Lambda 表达式创建线程Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000); // 子线程每秒打印一次} catch (InterruptedException e) {e.printStackTrace();}}});t.start(); // 启动子线程// 主线程的任务while(true){System.out.println("hello main");Thread.sleep(1000); // 主线程每秒打印一次}}
}
hread类的常见属性和构造方法:
- Thread类常见的构造方法:
- Thread类常见的属性:
public class Demo6 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"自定义线程");//->线程名字为自定义线程t.start();System.out.println("线程ID : " + t.getId());System.out.println("线程名字: " + t.getName());System.out.println("线程状态: " + t.getState());System.out.println("线程执行顺序:" + t.getPriority());}
}
普通线程(前台线程):
public static void main1(String[] args) {Thread t1 = new Thread(() -> {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();
}
Thread t1 = new Thread(() -> { ... })
:通过 Lambda 表达式创建了一个线程,并定义了线程执行的任务。任务就是不断打印"hello thread"
,然后每隔 1 秒 (Thread.sleep(1000)
) 暂停。t1.start()
:启动线程。
在这种情况下,线程 t1
是一个普通线程,也就是 用户线程。程序结束时,所有的用户线程都必须结束,才能让 JVM 退出。
2. main
方法(后台线程)
public static void main(String[] args) {Thread t2 = new Thread(() -> {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//把 t 设置线程为后台线程(守护线程),不能阻止程序结束t2.setDaemon(true);t2.start();
}
-
t2.setDaemon(true)
:这一行代码将线程t2
设置为守护线程。守护线程是一个特殊的线程,它的作用是支持其他线程的工作。守护线程在没有其他用户线程(非守护线程)运行时会自动退出。换句话说,只要程序中的所有用户线程都结束了,JVM 就会退出,即使守护线程还在运行。- 普通线程(用户线程)会阻止 JVM 退出,直到它们执行完毕。
- 守护线程在所有用户线程结束后会立即退出。
-
t2.start()
:启动线程。
到这里竹竹零就要和大家说再见了
希望时光不负赶路人,愿我们做最好的自己!!!
如果您觉得有失偏颇请您在评论区指正,如果您觉得不错的话留个好评再走吧!!
您的鼓励就是对我最大的支持! ! !