JavaEE之多线程
2026/6/1 6:42:10 网站建设 项目流程

线程概念:

1)线程定义:线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际执行单元。每个线程都可以按照自己的顺序执行各自的代码,多个线程之间同时执行着多份代码。

2)为什么要有线程?:

单核cpu算力遇到瓶颈,需要多核cpu来提高算力。而并发编程可以解决这一问题。

其次,虽然多进程也可以实现并发编程,但是线程比进程更轻量,更具有优势:

1.创建线程比创建进程更快

2.销毁线程比销毁进程更快

3.调度线程比调度进程更快

所以大家通常会使用多线程

可以举一个形象的例子来形容多进程和多线程:

有一个房间,一张桌子,一个人和桌子上的一百只鸡,这个人的任务是将这一百只鸡吃完。

为了提高效率,引入多进程: 有两个房间,房间里分别有一个桌子,一个人和50只鸡,相比之前,效率会大幅度提升。

而多线程则是:一个房间,一张桌子,一百只鸡,两个人共同来完成这个任务,节省下了房间和桌子的开销,效率仍然能够大幅提高。

3.试想,如果引入更多的线程效率是否会进一步提高?

答案是否定的:

当线程数目太多,线程调度的开销也会进一步扩大,进而拖慢程序的性能。

4.在任务进行的过程中,如果有两个人同时看中了同一只鸡,就有可能产生“冲突”,导致线程不安全,有可能会使代码产生bug。当这两个人中的一个没能抢到鸡时,非常生气,就把桌子给掀了,我吃不了,大家都别吃了。

此时这个线程便会抛出异常(如果及时捕获处理掉,也不一定会导致进程终止),可能会带走整个进程,所有其他的线程都无法再继续工作。

3).进程和线程的区别:

1.进程包含线程,每个进程至少有一个线程存在,即主线程

2.进程与进程之间不共享内存空间,同一个进程的线程之间共享同一个内存空间。

3.进程是系统分配资源的最小单位,线程是系统调度的最小单位

4.一个进程挂了不会影响其他进程,但一个线程挂了,可能会把同进程内的其他线程一同带走(整个进程崩溃)

4)创建线程的四种写法:

4.11.继承Thread,重写run
  1. 首先定义一个类(这里我的类名为MyThread),这个类需要继承Thread
  2. 然后需要重写run方法,run方法内部就是我们要执行的线程代码
  3. 最后启动线程
class MyThread extends Thread{ @Override public void run(){ while(true){ System.out.println("hello Thread"); try { Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } } } public class Demo1 { public static void main(String[] args) throws InterruptedException { //创建Thread类的子类,在子类中重写Run方法 Thread t = new MyThread(); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

在Thread父类中,本身有一个run方法,而我们可以重写这个方法,编写属于自己的逻辑

该代码中共有两个线程:

1.由newThread创建并调用t.start()启动,进入while循环后每秒打印“hello Thread”.

2.主线程(main):进入while循环每秒打印“hello main”

4.111sleep简单介绍:

sleep为休眠,意味着让当前线程放弃cpu,休息1000毫秒后再开始执行该任务。

注:

调用sleep会让当前线程处于阻塞状态(TIMED_WAITING),在此期间,其他线程会调用该线程的Interrupt()方法来中断它的休眠,被中断后,sleep便会抛出InterruptedException异常,从而让线程有机会响应中断请求。

而Java要求强制要求处理这个异常,通常在run方法中用try-catch或在方法签名中throws异常。

运行结果:

我们可以看到,执行过程中,有的时候main方法在前,有的时候Thread在前,

这是因为多个线程,调度顺序是随机的(操作系统内核控制),无法预测执行顺序,称为抢占式调度。

将t.start改为t.run后,由于主线程中并没有创建新的线程,而run又只是一个普通方法调用,主线程会直接执行run方法,进入while死循环打印hellothread。
4.112: 可以借助第三方工具来查看线程的具体执行情况:

上方的图标代表线程数量,左下角的代表当前进程中的线程。

4.12. 实现Runnable方法,重写run
  1. 定义一个类实现Runnable接口
  2. 重写run方法
  3. 构建Thread对象,将创建的Runnable对象作为参数传入
  4. 启动线程(t.start)
class MyRunnable implements Runnable { @Override public void run() { while (true) { System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo2 { public static void main(String[] args) throws InterruptedException { Runnable runnable = new MyRunnable(); Thread t = new Thread(runnable); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

这两种方式推荐哪一个?

第二种,因为Runnable更加解耦合,方便后续代码的修改

耦合:两个代码的关联关系越大,耦合越大,推荐低耦合(后续代码如果出错,不会影响其他代码)

高内聚:将与某种逻辑关联的代码放到一块。(有条理),反之同理。

写代码的时候推荐低耦合高内聚。

4.13:使用Thread的匿名内部类:

1.创建一个Thread类的子类(匿名)

2.{}里编写子类的定义代码

3.创建这个匿名内部类的实例,将实例的引用赋值给t

public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }
4.14 使用Runnable的匿名内部类
public class Demo4 { public static void main(String[] args)throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } } }; Thread t = new Thread(runnable); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }
4.15:引入lambda表达式(推荐)
public class Demo5 { public static void main(String[] args)throws InterruptedException { Thread t = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch(InterruptedException e){ throw new RuntimeException(e); } } }); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

lambd表达式相当于是匿名内部类的替换写法,这种方法可以快速方便的就创建出一个线程

//lambda表达式本质上,是一个匿名函数(没有名字的函数,用一次就完了),主要来实现“回调函数”的效果

4.2 Thread类的其他属性和方法

前台线程与后台线程的区别:

前台线程:main线程及用户创建的线程,

后台线程:垃圾回收等辅助作用的线程(也叫做守护线程)

注:jvm会等待所有前台线程结束后,才会结束运行(不会等待后台进程)

前台线程好比一个酒桌中的话事人,而后台线程好比酒桌里的透明人,什么时候结束酒席由酒桌中的多个话事人决定。

4.21: 以下代码来形象的说明前台线程:

public class Demo6 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while (true) { System.out.println("hello 1"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); Thread t2 = new Thread(() -> { while (true) { System.out.println("hello 2"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t2.start(); Thread t3 = new Thread(() -> { while (true) { System.out.println("hello 3"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t3.start(); for (int i = 0; i < 3; i++) { System.out.println("hello main"); Thread.sleep(1000); } } }

虽然main线程结束了,但自己创建的3个前台线程还存在,所以进程还会存在,继续执行三个线程

4.22 IsAlive:判断线程是否存活:
public class Demo8 { public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } }); System.out.println(t1.isAlive()); //false 因为线程还没有创建 t1.start(); System.out.println(t1.isAlive()); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

在t1线程还未创建之前,线程不存在,false,创建之后,为true(线程存在)。

4.23: setDaemon: 设置为后台进程。
public class Demo7 { public static void main(String[] args) throws InterruptedException{ Thread t = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } }); t.setDaemon(true); t.start(); for (int i = 0; i <3 ; i++) { System.out.println("hello main"); Thread.sleep(1000); } System.out.println("线程结束"); } }

将前台线程t设置为后台线程后,当main线程(前台线程)三次执行完后,后台进程也会随之关闭

4.24:中断线程interrupt:

常见的有两种方法来实现:

1.通过共享的标记来沟通、

2.调用interrupt方法来沟通

1.使用自定义的变量来做标志位:

定义一个当作线程中断标志的变量,通过其他线程对这个变量的修改,实现线程中断:

public class Demo10 { private static boolean isFinished = false; public static void main(String[] args) throws InterruptedException{ Test test = new Test(); Thread t = new Thread(()->{ while(!isFinished) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("thread 结束"); }); t.start(); Thread.sleep(3000); isFinished = true; } }

通过设置一个成员变量isFinished,在线程执行3s后更改成员变量isfinished的值,从而中断t线程。

lambda表达式会自动捕获方法内,之前出现的变量

lambda表达式内使用的标志,必须是final或者常量

2.使⽤ Thread.interrupted()或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位
public class Demo11 { public static void main(String[] args) throws InterruptedException{ Thread t = new Thread(()->{ System.out.println(Thread.currentThread().getName()); while(!Thread.currentThread().isInterrupted()){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ //throw new RuntimeException(e); break; } } System.out.println("t线程结束"); }); t.start(); Thread.sleep(3000); System.out.println("main线程尝试终止t线程" ); t.interrupt(); } }

Thread.currentThread(),静态方法,哪个线程调用,就获取到哪个线程的引用

isInterrupted,是为了判断线程是否被中断,thread变量里的Boolean值,中断为true,反之为false。

而interrupt方法会发送中断请求,修改Boolean变量里的值,还能唤醒像sleep这样的阻塞方法。

以上就是博主对线程知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询