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

1. 什么是线程

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

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

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

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

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

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

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

3. 多线程使用场景

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

  • 迅雷的下载

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

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

方法 1:继承 Thread

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

示例

java 复制代码
class MyThread extends Thread {
    @Override
    public 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();  // 启动线程1
        thread2.start();  // 启动线程2
    }
}

方法 2:实现 Runnable 接口

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

示例

java 复制代码
class MyRunnable implements Runnable {
    @Override
    public 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();  // 启动线程1
        thread2.start();  // 启动线程2
    }
}

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

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

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

start()

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

java 复制代码
与之前的方式的对比:与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)。
  1. 通过 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 状态。
  1. RUNNABLE(可运行状态)
  • 描述:线程处于可运行状态,可以被调度执行,但并不一定在运行。此状态包括两个子状态:正在运行和就绪。线程调度器决定哪个线程在 CPU 上运行。
  • 状态转移
    • 当线程调用 start() 时,从 NEW 转换为 RUNNABLE。
    • 当线程执行 sleep()wait()join() 或其他阻塞操作时,线程会从 RUNNABLE 转移到 WAITING 或 TIMED_WAITING 状态。
    • 当线程获取到锁后,可能会从 BLOCKED 状态转移回 RUNNABLE。
  1. BLOCKED(阻塞状态)
  • 描述:线程在尝试获取一个已被其他线程持有的锁时,进入 BLOCKED 状态。此时线程无法继续执行。
  • 状态转移:当线程获取到所需的锁时,BLOCKED 状态会转移到 RUNNABLE 状态。
  1. WAITING(等待状态)
  • 描述 :线程在等待另一个线程执行特定操作(如 wait()join()LockSupport.park())时,进入 WAITING 状态。此状态下线程不会占用 CPU 资源。
  • 状态转移
    • 当调用的线程被唤醒(如通过 notify()notifyAll())时,或者被中断时,线程将转移回 RUNNABLE 状态。
  1. TIMED_WAITING(计时等待状态)
  • 描述 :线程在等待特定时间后自动返回的状态。这可以由调用 Thread.sleep(millis)Object.wait(timeout)Thread.join(timeout) 等引起。
  • 状态转移:在指定的时间到达后,线程会转移回 RUNNABLE 状态。
  1. TERMINATED(终止状态)
  • 描述:线程已完成执行,或由于异常终止。此时线程的生命周期结束,无法再执行。
  • 状态转移 :线程在完成 run() 方法的执行或抛出未捕获异常后进入此状态。

状态之间的关系

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

java 复制代码
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有什么不同

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

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

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

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

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

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

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

java 复制代码
1. 如何看待死锁?
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
我们编写程序时,要避免出现死锁。

2. 诱发死锁的原因?
- 互斥条件
- 占用且等待
- 不可抢夺(或不可抢占)
- 循环等待

以上4个条件,同时出现就会触发死锁。

3. 如何避免死锁?
针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

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

notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同,则

随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

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

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

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

java 复制代码
相同点:一旦执行,当前线程都会进入阻塞状态

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

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

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

单例模式实现

java 复制代码
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() 时都进行同步,从而提高性能。

使用示例

java 复制代码
public class Main {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        System.out.println(singleton1 == singleton2); // 输出 true,确保是同一实例
    }
}
相关推荐
若鱼191938 分钟前
gRPC-集成Springboot
java
猫爪笔记40 分钟前
JAVA基础:数组 (习题笔记)
java·开发语言·笔记·学习
尘浮生2 小时前
Java项目实战II基于Spring Boot的智慧生活商城系统的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·mysql·maven·生活
疯一样的码农4 小时前
Java初学者指南
java·开发语言
LUwantAC4 小时前
Java学习路线:JUL日志系统(一)日志框架介绍
java·开发语言·学习
佚先森5 小时前
IAPP仿源码大师主界面UI
java
q567315236 小时前
使用 Python 编辑 XML 文件中的文本字段
xml·java·数据库·python·sqlite
LeMay086 小时前
基础算法——排序算法(冒泡排序,选择排序,堆排序,插入排序,希尔排序,归并排序,快速排序,计数排序,桶排序,基数排序,Java排序)
java·算法·排序算法
Mr。轩。6 小时前
cn.afterturn.easypoi.exception.excel.ExcelExportException: Excel导出错误 -> 修正过程。
java·excel·导出
大山很山6 小时前
Python简介和程序设计思想 |【python技能树知识点1~2】
java·网络·python