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

java基础知识面试题四多线程

1. 什么是线程

  • 线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。

    • 一个进程同一时间若并行执行多个线程,就是支持多线程的。

    • 线程作为CPU调度和执行的最小单位

    • 一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

2. 线程和进程有什么区别

进程:对应一个运行中的程序。

线程:运行中的进程的一条或多条执行路径。

3. 多线程使用场景

  • 手机app应用的图片的下载

  • 迅雷的下载

  • Tomcat服务器上web应用,多个客户端发起请求,Tomcat针对多个请求开辟多个线程处理

4. 如何在Java中出实现多线程?

方法 1:继承 Thread

  1. 创建一个子类 继承自 Thread
  2. 重写 run() 方法,在其中定义线程要执行的代码。
  3. 创建线程实例 并调用 start() 方法启动线程。

示例

class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + " - Count: " + i);try {Thread.sleep(500);  // 暂停500毫秒} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadExample {public static void main(String[] args) {MyThread thread1 = new MyThread();MyThread thread2 = new MyThread();thread1.start();  // 启动线程1thread2.start();  // 启动线程2}
}

方法 2:实现 Runnable 接口

  1. 创建一个类 实现 Runnable 接口。
  2. 重写 run() 方法,定义线程要执行的代码。
  3. 创建 Thread 实例,将 Runnable 对象作为参数传递给 Thread
  4. 调用 start() 方法启动线程

示例

class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + " - Count: " + i);try {Thread.sleep(500);  // 暂停500毫秒} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class RunnableExample {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread1 = new Thread(myRunnable);Thread thread2 = new Thread(myRunnable);thread1.start();  // 启动线程1thread2.start();  // 启动线程2}
}

5. Thread类中的start()和run()有什么区别?

  • start():创建并启动一个新线程,在新线程中执行 run() 方法,支持并发执行。
  • run():是线程执行的逻辑,如果直接调用则不会启动新线程,而是在当前线程中执行。

6. 启动一个线程是用run()还是start()?

start()

7. Java中Runnable和Callable有什么不同?

与之前的方式的对比:与Runnable方式的对比的好处
> call()可以有返回值,更灵活
> call()可以使用throws的方式处理异常,更灵活
> Callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活有缺点吗?如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。

8. 什么是线程池,为什么要使用它?

线程池是一个管理、控制多个工作线程的技术,用于减少线程的创建与销毁次数,提高系统资源的使用效率。在 Java 中,线程池由 java.util.concurrent 包中的 Executor 框架提供,通过 ThreadPoolExecutor 实现具体的线程池管理。

线程池的作用

  1. 减少资源消耗:线程池复用已创建的线程,避免频繁创建、销毁线程带来的系统资源消耗。
  2. 提高响应速度:当有新任务时,不必等待线程创建即可执行,减少了任务的等待时间。
  3. 提高线程管理的可控性:线程池可以根据系统资源情况设置线程数量的上限和下限,避免过多线程导致资源耗尽。
  4. 提供定时任务和周期性任务执行的功能:线程池可以定期或周期性地执行任务,适用于需要周期性运行的任务。

线程池的工作原理

  1. 任务提交:任务被提交给线程池后,线程池会将其放入任务队列中。
  2. 线程执行任务:线程池中的空闲线程会从任务队列中取出任务并执行。如果没有空闲线程而线程数量低于最大线程数,线程池会创建新线程。
  3. 任务完成后:线程将返回线程池中待命,等待执行新的任务。

Java 中的线程池类型

Java 提供了几种常见的线程池:

  1. FixedThreadPool:固定大小的线程池,适用于执行长期任务和控制线程数量的场景。
  2. CachedThreadPool:适合执行大量、短期的异步任务。线程池会根据需要创建新线程,空闲线程超过 60 秒会被销毁。
  3. ScheduledThreadPool:支持定时和周期性执行任务。
  4. SingleThreadExecutor:单线程化的线程池,保证所有任务按顺序执行。

9. sleep() 和 yield()区别?

  • sleep():使线程暂停执行,进入阻塞状态,直到指定时间结束,线程会释放 CPU 资源。
  • yield():让当前线程暂时放弃 CPU 时间,转为就绪状态,以允许其他线程执行,但不一定会立即导致其他线程的执行。

10. 线程创建的中的方法、属性情况?

在 Java 中,线程的创建和管理涉及多个方法和属性。这里主要讨论通过 Thread 类和 Runnable 接口创建线程时常用的方法和属性。

1. 通过 Thread 类创建线程

方法

  • start():启动线程,调用 run() 方法。在新线程中执行代码。
  • run():线程执行的主体代码。如果直接调用 run(),则不会启动新线程。
  • sleep(long millis):使当前线程休眠指定的时间,进入阻塞状态。
  • join():使当前线程等待直到被调用的线程完成执行。
  • interrupt():中断线程的执行。
  • setPriority(int priority):设置线程的优先级(范围为 1 到 10)。
  • getName():获取线程的名称。
  • setName(String name):设置线程的名称。

属性

  • Thread.MIN_PRIORITY:最小优先级,值为 1。
  • Thread.NORM_PRIORITY:默认优先级,值为 5。
  • Thread.MAX_PRIORITY:最大优先级,值为 10。
  • Thread.State:线程的状态(如 NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED)。

2. 通过 Runnable 接口创建线程

方法

  • run():实现 Runnable 接口时,定义线程执行的逻辑。在 Thread 对象中通过 start() 方法启动线程。
  • 创建 Thread 对象:在创建 Thread 时,将实现了 Runnable 的类的实例传递给 Thread 构造函数。

11. 线程的生命周期?

总结

  • Java 线程的生命周期主要包括 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 和 TERMINATED 状态。
  • 线程的状态转移依赖于线程的执行情况、等待条件和锁的获取情况。理解这些状态对调试和优化多线程应用至关重要。

12. 线程的基本状态以及状态之间的关系?

线程的基本状态及其相互关系在 Java 中主要体现在以下几种状态:

1. NEW(新建状态)

  • 描述:线程被创建但尚未启动。此时,线程并没有分配 CPU 资源。
  • 状态转移:通过调用 start() 方法,线程从 NEW 状态转变为 RUNNABLE 状态。

2. RUNNABLE(可运行状态)

  • 描述:线程处于可运行状态,可以被调度执行,但并不一定在运行。此状态包括两个子状态:正在运行和就绪。线程调度器决定哪个线程在 CPU 上运行。
  • 状态转移
    • 当线程调用 start() 时,从 NEW 转换为 RUNNABLE。
    • 当线程执行 sleep()wait()join() 或其他阻塞操作时,线程会从 RUNNABLE 转移到 WAITING 或 TIMED_WAITING 状态。
    • 当线程获取到锁后,可能会从 BLOCKED 状态转移回 RUNNABLE。

3. BLOCKED(阻塞状态)

  • 描述:线程在尝试获取一个已被其他线程持有的锁时,进入 BLOCKED 状态。此时线程无法继续执行。
  • 状态转移:当线程获取到所需的锁时,BLOCKED 状态会转移到 RUNNABLE 状态。

4. WAITING(等待状态)

  • 描述:线程在等待另一个线程执行特定操作(如 wait()join()LockSupport.park())时,进入 WAITING 状态。此状态下线程不会占用 CPU 资源。
  • 状态转移
    • 当调用的线程被唤醒(如通过 notify()notifyAll())时,或者被中断时,线程将转移回 RUNNABLE 状态。

5. TIMED_WAITING(计时等待状态)

  • 描述:线程在等待特定时间后自动返回的状态。这可以由调用 Thread.sleep(millis)Object.wait(timeout)Thread.join(timeout) 等引起。
  • 状态转移:在指定的时间到达后,线程会转移回 RUNNABLE 状态。

6. TERMINATED(终止状态)

  • 描述:线程已完成执行,或由于异常终止。此时线程的生命周期结束,无法再执行。
  • 状态转移:线程在完成 run() 方法的执行或抛出未捕获异常后进入此状态。

状态之间的关系

下面是线程状态及其之间转移的简要概述:

NEW -> RUNNABLE (通过 start())
RUNNABLE -> BLOCKED (等待锁)
RUNNABLE -> WAITING (调用 wait()/join())
RUNNABLE -> TIMED_WAITING (调用 sleep()/wait(timeout)/join(timeout))
BLOCKED -> RUNNABLE (获取到锁)
WAITING -> RUNNABLE (被唤醒或中断)
TIMED_WAITING -> RUNNABLE (时间到)
RUNNABLE -> TERMINATED (完成或异常)

13. stop()和suspend()方法为何不推荐使用?

stop():一旦执行,线程就结束了,导致run()有未执行结束的代码。stop()会导致释放同步监视器,导致线程安全问题。

suspend():与resume()搭配使用,导致死锁。

14. Java 线程优先级是怎么定义的?

在 Java 中,线程优先级是通过 Thread 类中的常量来定义的,主要用于帮助线程调度器决定线程的执行顺序。线程优先级的范围是 1 到 10,具体的常量定义如下:

线程优先级常量

  • Thread.MIN_PRIORITY:最小优先级,值为 1。
  • Thread.NORM_PRIORITY:默认优先级,值为 5。
  • Thread.MAX_PRIORITY:最大优先级,值为 10。

设置线程优先级

可以通过 setPriority(int priority) 方法来设置线程的优先级。优先级较高的线程会比优先级较低的线程获得更多的 CPU 时间,但这并不保证高优先级的线程一定会先执行,具体行为依赖于底层操作系统的线程调度策略。

15. 你如何理解线程安全的?线程安全问题是如何造成的?

线程安全的理解

线程安全是指在多线程环境中,多个线程同时访问某个对象或资源时,不会导致数据不一致或状态错误的情况。当一个类或方法被称为线程安全时,意味着它能够在多线程环境下正常工作,不需要外部同步机制来确保正确性。

线程安全的特点

  • 原子性:线程安全的操作是不可分割的,要么完全执行,要么完全不执行。
  • 可见性:当一个线程对共享变量进行修改时,其他线程能够立即看到这个修改。
  • 一致性:多线程操作某个对象时,状态始终保持一致。

线程安全问题的产生原因

线程安全问题通常源于以下几个方面:

  1. 共享资源的访问

    • 多个线程同时访问和修改共享资源(如变量、对象或数据结构)时,可能会导致数据冲突或不一致。
  2. 不适当的同步机制

    • 如果对共享资源的访问没有适当的同步(如使用 synchronized 关键字、Lock 接口等),则多个线程可能会同时读取和修改数据,导致状态错误。
  3. 顺序执行的假设

    • 多线程程序通常假设操作是按顺序执行的,但实际执行可能并不遵循这一假设。比如,一个线程在读取数据时,另一个线程可能已经修改了这些数据。
  4. 不可变对象的使用不足

    • 如果使用的对象不是不可变的,多个线程同时修改同一个对象的状态,会导致不一致。
  5. 中间状态的可见性

    • 在线程间共享的变量在一个线程中修改后,另一个线程未必能立即看到该修改,可能导致读取到中间状态。

16. 多线程共用一个数据变量需要注意什么?

在多线程环境中共享数据变量时,需要注意同步控制、原子性、可见性、避免死锁、减少共享状态、确保数据一致性以及使用合适的并发数据结构。这些注意事项可以帮助开发者避免多线程编程中的常见问题,确保程序的稳定性和正确性。

17. 多线程保证线程安全一般有几种方式?

在多线程环境中保证线程安全可以通过使用同步机制、显式锁、原子变量、线程安全的集合、线程局部变量、无锁编程以及良好的并发设计模型等多种方式来实现。选择适当的方法应根据具体应用场景和性能需求进行。

18. 用什么关键字修饰同步方法?

synchronized

19. synchronized加在静态方法和普通方法区别

  • 普通方法的 synchronized 锁定当前对象的实例,适用于保证同一实例的线程安全。
  • 静态方法的 synchronized 锁定类的 Class 对象,适用于保证类级别的线程安全。选择使用哪种方式取决于具体的应用场景和设计需求。

20. Java中synchronized和ReentrantLock有什么不同

synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。
Lock是通过两个方法控制需要被同步的代码,更灵活一些。
Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。

21. 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

需要看其他方法是否使用synchronized修饰,同步监视器的this是否是同一个。

只有当使用了synchronized,且this是同一个的情况下,就不能访问了。

22. 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?

同步一定阻塞;阻塞不一定同步。

23. 什么是死锁,产生死锁的原因及必要条件

1. 如何看待死锁?
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
我们编写程序时,要避免出现死锁。2. 诱发死锁的原因?
- 互斥条件
- 占用且等待
- 不可抢夺(或不可抢占)
- 循环等待以上4个条件,同时出现就会触发死锁。3. 如何避免死锁?
针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

24. Java中notify()和notifyAll()有什么区别

notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同,则
         随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。 

25. 为什么wait()和notify()方法要在同步块中调用

因为调用者必须是同步监视器。

26. wait()和sleep()有什么区别?调用这两个函数后,线程状态分别作何改变?

相同点:一旦执行,当前线程都会进入阻塞状态不同点:
> 声明的位置:wait():声明在Object类中sleep():声明在Thread类中,静态的
> 使用的场景不同:wait():只能使用在同步代码块或同步方法中sleep():可以在任何需要使用的场景
> 使用在同步代码块或同步方法中:wait():一旦执行,会释放同步监视器sleep():一旦执行,不会释放同步监视器
> 结束阻塞的方式:wait(): 到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞sleep(): 到达指定时间自动结束阻塞

26. 手写一个单例模式(Singleton),还要安全的

下面是一个线程安全的单例模式(Singleton)的实现,采用的是 双重检查锁定(Double-Checked Locking)的方法。这种方法在多线程环境中既保证了线程安全,又避免了不必要的同步开销。

单例模式实现

public class Singleton {// 使用 volatile 关键字保证可见性和禁止指令重排private static volatile Singleton instance;// 私有构造函数,防止外部实例化private Singleton() {// 防止反射攻击if (instance != null) {throw new IllegalStateException("Already instantiated");}}// 获取实例的方法public static Singleton getInstance() {// 第一次检查if (instance == null) {synchronized (Singleton.class) {// 第二次检查if (instance == null) {instance = new Singleton();}}}return instance;}
}

解释

  1. 私有构造函数:确保外部无法直接创建实例。
  2. volatile 关键字:用于确保 instance 在多线程环境中的可见性,防止指令重排带来的问题。
  3. 双重检查锁定
    • 第一次检查 instance 是否为 null,如果是,进入同步块。
    • 在同步块内再次检查 instance 是否为 null,如果仍然是 null,则创建实例。这种方式可以避免在每次调用 getInstance() 时都进行同步,从而提高性能。

使用示例

public class Main {public static void main(String[] args) {Singleton singleton1 = Singleton.getInstance();Singleton singleton2 = Singleton.getInstance();System.out.println(singleton1 == singleton2); // 输出 true,确保是同一实例}
}

http://www.mrgr.cn/news/64108.html

相关文章:

  • 计算机组成原理之CPU的功能与基本结构
  • 亚马逊降佣刺激印度市场,中小卖家利好消息,测评助力扬帆起航
  • 【STM32】SD卡
  • mysql8 window 免安装
  • Mybatisplus多表关联分页查询有多种实现方式
  • 内网穿透技术选型PPTP(点对点隧道协议)和 FRP(Fast Reverse Proxy)
  • 手写实现call,apply,和bind方法
  • 【MATLAB代码】三个CT模型的IMM例程,各CT旋转速率不同,适用于定位、导航、目标跟踪
  • 2024阿里云CTF Web writeup
  • 房贷利率定价调整机制变更的一点理解
  • Linux 进程终止 进程等待
  • 面试必会50题
  • MATLAB口罩检测
  • 大学城水电管理:Spring Boot应用案例
  • Ollama:本地部署与运行大型语言模型的高效工具
  • static全局/局部变量/函数和普通全局/局部变量/函数的区别
  • 赋值语句@赋值表达式@便于阅读和便于理解的比较
  • 【Linux中的第一个小程序】进度条及printf打印彩色字符
  • 《Python修炼秘籍》01踏上编程之旅
  • 满秩分解与奇异值分解
  • 机器人大模型GR2——在大规模视频数据集上预训练且机器人数据上微调,随后预测动作轨迹和视频(含GR1详解)
  • 树的遍历(先,中,后)
  • 【无人机设计与控制】改进无人机三维路径规划(蜣螂优化算法)Matlab程序
  • 除甲醛开窗通风的正确方法 消除甲醛的最好方法
  • 如何引用一个已经定义过的全局变量?
  • 【含文档】基于ssm+jsp的智慧篮球馆预约(含源码+数据库+lw)