当前位置: 首页 > news >正文

线程1——javaEE 附面题

   

目录

         

引入

进程

进程和线程的关系

线程

创建线程

1.通过创建 Thread 子类

2.通过实现 Runnable接口

3.子类匿名表达式

4.匿名内部类(Runnable)

启动线程  

Thread 类的常用属性(方法)

线程的状态

线程休眠

打断线程

线程等待 join

线程安全问题

锁synchronized()

先从使用入手

特性

wait/notify

使用

wait使用

两者搭配

面经:


     

引入

         线程的概念离不开cpu(中央处理器),CPU 作为电脑的“脑”,其算力是非常夸张的但它是如何工作的呢,在认识线程前我们先来了解一下 CPU。


         计算机祖师爷冯诺依曼提出的冯诺依曼体系结构(运算器,控制器,存储设备,输入设备,输出设备)奠定了现代计算机的硬件基础。运算器,控制器便是CPU最基础,最核心的功能。

cpu 执行是很复杂的 可以简化成

1. 读取指令        -------------------------------------------------            指令(机器语言 )

2. 解析指令        -------------------------------------------------            从指令表中对应查找指令是什么意思

3. 执行指令        -------------------------------------------------            运算

现代 多核 cpu 下诞生了进程

进程

进程是操作系统中资源分配的基本单位。

操作系统是一个描述系统资源,通过一定数据结构组织资源的管理软件。     、


                                                           系统通过PCB 来描述进程

PID同一台机器,同一时间,不重复
内容指针内存资源分为数据/指令 操作系统可通过其找到数据/指令
文件描述符表硬盘资源  打开文件可以得到一个文件描述符,打开多个就可以用数组/顺序表表示
状态就绪状态  /  阻塞状态
优先级依据重要性给进程分配资源
上下文进程调度出 cpu 时保存一个中间状态,保证进程再调度回来时可以恢复之前的工作
记账信息PCB 会记录下进程在CPU上执行的次数,分配写资源给使用资源少的进程

进程的创建/销毁开销(销毁时间和系统资源)非常大,在创建时申请资源(大开销操作),为了提高效率降低开销引入了线程。

进程和线程的关系

1.线程是更轻量的进程。(进程太重了大开销,低效率)
2.进程包含线程,一个线程有大于等于一个线程,不能没有。
3.同一个进程上的线程共享进程的资源。
4.每个线程都可以执行独立的逻辑,并在cpu上独立调度
5.当进程已经有了,在进程上创建线程可以省去申请资源的开销。

线程

线程是系统调度执行的基本单位。       
线程满足了“并发编程” 使一个服务器可以同时处理多个客户端的访问。          


线程虽更轻量,多线程可以提升效率,但过犹不及。
线程过多带来的问题:
1.线程过多时,线程的创建和销毁时的开销就不可忽视了。
2.多线程环境下,多个线程对同一个变量同时进行操作。
     多对一 可读不可取
     一对一 可读又可取

3.线程中断会抛出异常,如果没有被捕获到,进程就会崩溃,线程会全挂掉。
 

创建线程

1.通过创建 Thread 子类

重写 run(); 方法,创建Thread 对象,调用start();

代码实现:

class myThread extends Thread{public void run (){System.out.println("hello thread");}
}
public class demo1 {public static void main(String[] args) {Thread thread = new myThread();thread.start();System.out.println("hello main");}
}

👀输出:

通过写入无限循环观察下:

代码:

class myThread extends Thread{@Overridepublic void run() {while(true){try {System.out.println("hello thread");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}public class demo1 {public static void main(String[] args) {Thread thread = new myThread();thread.start();while(true){try {System.out.println("hello main");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

👀输出

观察结果可以发现 main 主线程 和 我们自己创建的 thread 线程是并行执行的,顺序由cpu调度决定,随机出现。

2.通过实现 Runnable接口

再将 runnable 实例作为参数传给Thread 构造方法

代码:

class myRunnable implements Runnable{@Overridepublic void run() {System.out.println("hello thread");}
}    
public class demo2 {public static void main(String[] args) {Runnable runnable = new myRunnable(); Thread thread = new Thread(runnable);thread.start();System.out.println("hello main");}
}

👀输出:

这样的写法分离了任务逻辑和线程管理,不依赖于具体的类,使得后续可以轻松替换任务实现,降低程序的耦合度。

3.子类匿名表达式

代码: Thread t = new Thread () {

                   public void run(){

                   }

};

public class demo3 {public static void main(String[] args) {// 匿名内部类 是Thread的子类 重写了run方法Thread t = new Thread(){public void run(){while(true){try {System.out.println("hello thread");Thread.sleep(1000);  //  休眠1000ms  降低打印速度方便观察} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();System.out.println("hello main");}}

👀输出:

4.匿名内部类(Runnable)

代码:

Runnable runnable = new Runnable({

});

Thread t  = new Thread(runnable);

public class demo4 {public static void main(String[] args) {Runnable runnable = new Runnable(){@Overridepublic void run() {while(true){try {System.out.println("hello thread");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}; Thread t = new Thread(runnable);t.start();while(true){try {System.out.println("hello main");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

👀输出:

启动线程  

Thread.start();            start() 是 Thread 类的一个静态方法,可以直接用类名调用。

1.调用 start 会真正调用系统中创建线程的 api   start 执行不会产生阻塞,按代码顺序立刻向下执行。

2.一个线程只能 start 一次。  start 后 线程要么是就绪,要么是阻塞 不能重新 启动 了。

3.start 执行会自动执行 run() 方法。

Thread 类的常用属性(方法)

方法类别方法名功能描述补充说明
IDgetId()获取线程唯一标识符每个线程都有一个唯一的标识符,由 JVM 分配,从 1 开始递增
名称getName()获取线程名称线程创建时可以指定名称
状态getState()获取线程状态返回线程当前状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)
优先级getPriority()获取线程优先级线程优先级设置效果受操作系统调度机制影响,"改了不一定有用",
守护线程isDaemon()判断是否为守护线程守护线程会在所有非守护线程结束后自动终止,主要用于后台支持任务
存活状态isAlive()判断线程是否存活返回 false 表示线程未启动(NEW 状态)或已结束(TERMINATED 状态)
中断状态isInterrupted()判断线程是否被中断不会清除中断标记,需要通过 Thread.interrupted () 静态方法清除中断标记

注:isDaemon()    是否为后台线程
后台线程: 当线程没执行完时,进程要结束,线程无法阻止当前进程结束。
前台线程: 当前线程没执行完,进程要结束要等线程执行完,这样的线程成为前台线程。

线程的状态

New创建了线程对象但还没start   isAlive  false
TERMINED执行完成了(run完了)但对象还在  isAlive  false
WAITING死等  join 无参未设置超时时间
TIME_WAITING有时间的等  join 设置了超时时间
BLOCKED锁竞争产生的阻塞

线程休眠

sleep(时间 ms)  Thread 类的静态方法 让线程休眠多少毫秒后(进入Time_WAITING)
sleep 线程进入阻塞,调度出CPU ,唤醒后变为就绪状态,但不会立即执行,等待CPU调度。

打断线程

希望线程提前结束(sleep 时提前唤醒)

1.通过变量修改

2.通过 isInterrupted 标志位

查看当前中断状态:Thread.currentThread().isInterrupted()  不清除中断标志
检查中断状态:Thread.interrupted()  清除中断标志

若线程处于休眠sleep,会抛出InterruptedException 异常,需要 catch

try{Thread.sleep(1000);
}catch (InterruptedException e) {Thread.currentThread.interrupt();
}

线程等待 join

控制线程之间的执行顺序。

有两个版本的join
1.join();                  死等
2.join(超时时间)       等待到超时时间后就不等了

线程 t1      线程t2
t1.join();          t2等t1执行完
t1.join(1000)   t2等待t1执行完,等了1000ms t1还没结束就不等了   

⭐谁调用谁被等

线程安全问题

why:❓❓❓
1. 【根本原因】操作系统的随即调度,抢占式执行。
2. 操作不是原子的。  (原子的:不可再分的最小操作)
3. 多个变量同时操作同一个变量。
4. 内容不可见
4. 指令重排序

        面对非原子的操作,多线程就会出现多线程做同一个操作但做的是这个操作的不同部分
怎么理解呢? 就好像把一个大象放进冰箱需要几步。(这就是非原子操作是可拆分的)
                       1.打开冰箱门  
                       2.把大象放进去
                       3.关上冰箱门
        这时候如果有多个线程同时进行把同一只大象放进冰箱的操作。就有可能线程一打开了冰箱门被调度出CPU,线程2也执行了打开冰箱门,重复开门冰箱们受不了,线程2把大象放进冰箱并关上了冰箱门然后被调度出CPU,线程一被调度回CPU读取了中间状态继续之前的操作,把大象放进去,关上冰箱门。

        把一只大象放进去了两遍也就是BUG出现了,有人要问最后不还是大象在冰箱里面吗,但无效的操作消耗了资源,在这虽然没造成什么严重后果但这要是转账操作呢,同时扣了两次款呢?

how:
那怎么保证安全呢?
        既然是非原子操作造成的那可以把操作打包成原子的,java中提供了synchronized 可以给操作加锁保证线程一次把该执行完的逻辑执行完,这时有其他线程来执行这个操作就会触发锁竞争,产生阻塞等待上一个执行此操作的线程执行完解锁才能拿到锁,开始执行该操作。

锁synchronized()

先从使用入手

synchronized 有两个大括号
进入第一个大括号表示锁已经加上了,
从第二个大括号出来就表示解锁了。

通过加锁操作可以把操作变成原子的。

原理:加同一把锁的线程(锁对象是相同的)会竞争同一把锁(锁竞争)没抢到的阻塞等待抢到的解锁再抢。     

锁对象  Object locker = new Object(); 

1.锁

synchronized(锁对象){// 操作}

2.修饰普通方法

synchronized public void 方法(){//     this是锁对象}

3.修饰静态方法

synchronized public static void 方法(){// static 没有this ,所以锁对象是类对象// 类对象 .class
}

特性

1.互斥

当一个线程已经拥有锁的时候,该线程的锁不能被抢占。

2.可重入

当一个线程已经拥有一把锁的情况下,对于已有的这把锁可以重复加锁多次(连续加同一把锁)且不会触发死锁。

wait/notify

Object类的方法

协调线程之间执行的顺序  区别 join 控制线程间结束顺序

wait 会使线程释放锁主动阻塞等待,直到被notify 唤醒
eg:  
希望t1先执行再让t2执行
使用wait主动让t2阻塞让t1先参与调度,等t1执行完用notify唤醒t2.

使用

Object object = new Object ();
object.wait();   //   wait() 会阻塞 可能会抛出 InterruptedException  当执行wait时其内部会第一时间把锁放了

放锁的前提得先有一把锁,wait放锁后当前线程就会进入阻塞状态  WAITING  ,等待被唤醒,被唤醒后会再重新尝试去获取之前的锁,就会引发锁竞争 BLOCKED (被唤醒了,等拿到锁就会继续执行)

所以wait 应该搭配synchronized使用

wait使用
Object object = new Object();
try{synchronized(object){object.wait();}
}catch(InterruptedExecption e){
两者搭配

创建两个线程,线程t1等待,线程t2来唤醒t1

import java.util.Scanner;public class demo_notify {public static Object locker = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("t1 等待");synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1 等待后");});Thread t2 = new Thread(() -> {System.out.println("请输入任意内容唤醒t1");Scanner scanner = new Scanner(System.in);scanner.next();synchronized (locker) { locker.notify();}});t1.start();t2.start(); }}

👀输出

当多个线程都在 wait 时(同一个对象),此时 notify 会随机唤醒一个。使用 notifyAll 可以唤醒所有的。

import java.util.Scanner;public class demo_notifyAll {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {System.out.println("t1 等待前");synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1 等待后");});Thread t2 = new Thread(() -> {System.out.println("t2 等待前");synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t2 等待后");});Thread t3 = new Thread(() -> {System.out.println("请输入任意内容唤醒t1或t2");Scanner scanner = new Scanner(System.in);scanner.next();synchronized (locker) {locker.notify();}});t1.start();t2.start();t3.start();}}

输出:此时t3只会唤醒t1或t2其中一个。我这里运行唤醒了t2

想同时唤醒t1,t2就需要用notifyAll
 

import java.util.Scanner;public class demo_notifyAll {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {System.out.println("t1 等待前");synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1 等待后");});Thread t2 = new Thread(() -> {System.out.println("t2 等待前");synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t2 等待后");});Thread t3 = new Thread(() -> {System.out.println("请输入任意内容唤醒t1或t2");Scanner scanner = new Scanner(System.in);scanner.next();synchronized (locker) {locker.notifyAll();}});t1.start();t2.start();t3.start();}}

输出:

面经:

谈谈sleep 和 wait 的区别。
答:
1.wait 的设计是为了提前唤醒,超时时间只是作为Plan B。
   sleep 的设计就是为了到时间唤醒,虽可用 interrupt 提前唤醒但这样的唤醒会产生异常。

2.wait 需要搭配锁使用,因为执行时会先释放锁。(避免其他线程一直拿不到锁)
   sleep 就不需要搭配锁使用,当sleep 被放到synchronized中时,不会释放锁而是抱着锁睡。
 

多线程实用但充满陷阱未完待续。

                爱是个什么东西,它太理想主义,爱有什么了不起,我充满许多怀疑   
                                                                                                                    爱是个什么东西  DT
                                                                   ⭐❤️👍

http://www.dtcms.com/a/456661.html

相关文章:

  • 吴恩达机器学习课程(PyTorch适配)学习笔记:1.4 模型评估与问题解决
  • 后端_基于注解实现的请求限流
  • 从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 10--基础知识 6--元素等待方式和内联框架
  • 织梦网站如何做seo重庆市城市建设档案馆官方网站
  • 一文详解Go语言字符串
  • 通用:MySQL-LBCC并发锁机制
  • Elasticsearch:使用推理端点及语义搜索演示
  • 基于websocket的多用户网页五子棋(九)
  • Async++ 源码分析13--parallel_reduce.h
  • 分布式api调用时间优化和问题排查
  • LeetCode每日一题,20251008
  • h5网站建设的具体内容电子商务平台网站模板
  • hive sql优化基础
  • Linux小课堂: Linux 系统的多面性与 CentOS 下载指南
  • 详解redis,MySQL,mongodb以及各自使用场景
  • 开发网站设计公司建设通网站会员共享密码
  • Linux相关工具vim/gcc/g++/gdb/cgdb的使用详解
  • Verilog和FPGA的自学笔记2——点亮LED
  • uniapp创建ts项目tsconfig.json报错的问题
  • Linux性能调优之内核网络栈发包收包认知
  • 静态网站挂马u钙网logo设计影视剪辑
  • Rust 基础语法指南
  • C11 安全字符串转整数函数详解:atoi_s、atol_s、strtol_s 与 strtoimax_s
  • 从入门到实战:全面解析Protobuf的安装配置、语法规范与高级应用——手把手教你用Protobuf实现高效数据序列化与跨语言通信
  • SaaS版MES系统PC端后台功能清单与设计说明
  • 广州建立公司网站多少钱php网站培训机构企业做网站
  • 若依前后端分离版学习笔记(十九)——导入,导出实现流程及图片,文件组件
  • SSM后台投票网站系统9h37l(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 基于springboot高校汉服租赁系统的设计与实现(文末附源码)
  • 【AI大模型】WPS 接入DeepSeek 实战项目详解