面试02——线程、HashMap、、、

本文详细介绍了Java面试中常见的线程池、HashMap和并发控制相关问题。讨论了线程池的工作原理、核心参数、应用场景及线程的五种状态,分析了IO和多线程的关系,以及网络I/O的模型。同时,对HashMap的内部机制、线程安全问题进行了探讨,包括不同线程状态的控制方法,wait和sleep的区别,以及ThreadLocal的使用问题。此外,还涉及到了Spring Bean的生命周期、AOP、分布式锁等高频面试话题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、线程相关

1、线程池原理,讲讲线程池里面的核心参数,你平时是怎么用线程池的

基本思想:预先创建多个线程对象,放入线程池,执行完后调用,用完还原到线程池。

优点:

* 提高线程的利用率
* 提高程序的响应速度
* 便于统一管理线程对象
* 可以控制最大并发数
import org.junit.Test;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author cpx
 * @version V1.0
 * @date 2021/09/28
 **/
public class test {
   

    @Test
    public void threadLocal(){
   
        /**
         * corePoolSize:核心线程数(3个核心窗口)
         * maximumPoolSize:最大线程窗口(5个)
         * keepAliveTime:存活时间(1L)
         * TimeUnit:时间单位(秒)
         * ArrayBlockingQueue:等待队列(3个)
         * Executors.defaultThreadFactory():线程工厂
         * ThreadPoolExecutor.AbortPolicy():拒绝策略(直接抛异常)
         * 若i = 9 ,则拒绝超出最大线程数量
         */
        ExecutorService executor = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0 ; i<8 ; i++){
   
            executor.execute(()->System.out.println(Thread.currentThread().getName() + "===>办理业务"));
        }
        executor.shutdown();
    }
}


2、线程池的应用与配置(见1);

3、谈谈你理解的IO和多线程

IO:是一种数据的流从源头留到目的地。比如文件拷贝,输入流和输出流都包括了。输入流从文件中读取数据存储到进程中,输出流从进程中读取数据然后写入 到目标文件。


多线程:所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间。由于线程运行在单一进程中,因此共享这个进程虚拟地址空间的整个内容,包括代码、数据、堆、共享库和打开的文件。

多线程的优点:

  • 无序跨进城边界;
  • 程序逻辑和控制方式简单;
  • 所有线程可以直接共享内存和变量等;
  • 线程方式小号的总资源比进程方式好

缺点:

  • 每个线程与主线程序共用地址空间,受限于2GB地址空间;
  • 线程之间的同步和加锁控制比较麻烦;
  • 一个线程的崩溃可能影响整个程序的稳定性
  • 达到一定的线程数量程度后,即使在增加CPU也无法提高性能

进程:是一个正在执行中的程序。每一个进程执行都有一个执行路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行,一个进程至少有一个线程。

4、网络io什么的

4.1、网络I/O的4个要素

网络IO首先是一种I/O,按照我们上一节中对于“I/O”的定义,网络I/O也符合4个要素:

  • 源头(source):一台电脑的磁盘或内存;
  • 目标(target):另一台电脑的磁盘或内存;
  • 通道(channel):网线,WiFi等;
  • 液体(fluid):数据(通常是字节形式)。

4.2 、5种不同的I/O模型

然而网络I/O相对与其他I/O更为复杂,因此Unix提供了5种不同的I/O模型,分别是

  • 阻塞I/O(blocking I/O)
  • 非阻塞I/O(non-blocking I/O)
  • I/O复用(I/O multiplexing)
  • 信号驱动式I/O(signal-driven I/O)
  • 异步I/O(asynchronous I/O)

img

BIO

让我们先从BIO,即阻塞IO(blocking I/O)说起:

通常网络I/O在建立链接后,数据的传输通常包括俩个阶段

  • 阶段一:等待数据传输到本服务器;
  • 阶段二:内核将接受到的数据由"内核缓冲区"复制到“应用缓冲区”;

对应阻塞IO而言,这两个阶段又被分为了4个步骤:

  1. 用户的应用线程调用recvfrom,开始读取数据,进入阻塞状态;
  2. 系统内核等待接收网络传输的数据;(阶段一)
  3. 系统内核将传输的数据由内核缓冲区拷贝到应用缓冲区;(阶段二)
  4. 应用进程从阻塞中恢复,读取应用缓冲区的数据;
备注:
  1. “阻塞”可以理解为线程被内核“挂起”,让出CPU使用权,并等待被“唤醒”,重新获取CPU使用权;
  2. recvfrom()为Unix函数,类似于在JAVA编程中,我们使用read()来读取网络传输的数据;

img

BIO的缺点:

1.在数据传输与复制过程中,线程都处于阻塞状态,因此在存在大量访问链接时,应用必须启用多个线程进行处理,系统需要为每个线程都分配相应的内存资源;

2.线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间;

总结:BIO由于在数据传输与数据拷贝两个阶段,都需要阻塞线程,因此很难满足高并发,大量客户端同时访问服务端,传输数据的要求。因此在实际开发中,很少使用;或仅限于少量客户端链接的场景;

面试题:

BIO真的就一无是处了吗,哪些场景BIO的效率会更优秀呢?

答:我们说BIO是无法满足“大量链接”同时访问,传输数据的请求;但如果是链接数量较少,但单一链接的传输数据量较大的情况下,因为BIO省去了频繁询问的过程(这个后面NIO会讲),也是依然适用于这种模型的。

5、实现多线程有哪些方式

  • 第一种 实现Runnable接口,覆写run()方法
  • 第二种 继承Thread类,覆写run()方法
  • 第三种 利用Callable接口、Executors工具类、ExecutorService接口、Future接口实现有返回结果的多线程

5.1、第一种 实现Runnable接口,覆写run()方法

① 自定义类并实现Runnable接口,覆写run()方法

② 创建Thread对象,用实现了Runnable接口的类的实例对象作为参数实例化该Thread对象

③ 调用Thread的start()方法来启动线程

 1 package com.test;
 2 
 3 class MyThread implements Runnable{  // ①
 4     @Override
 5     public void run() {
 6         System.out.println("Thread body");
 7     }
 8 }
 9 public class MultiThread {
10     public static void main(String[] args){
11         Thread thread1 = new Thread(new MyThread());  // ②
12         Thread thread2 = new Thread(new MyThread());
13         thread1.start();  // ③
14         thread2.start();
15     }
16 }

5.2、第二种 继承Thread类,覆写run()方法

① 自定义类并继承Thread类,覆写run()方法

② 创建自定义类的实例对象

③ 调用自定义类对象的start()方法来启动线程

 1 package com.test;
 2 
 3 class MyThread extends Thread{  // ①
 4     @Override
 5     public void run() {
 6         System.out.println("Thread body");
 7     }
 8 }
 9 public class MultiThread {
10     public static void main(String[] args){
11         Thread thread1 = new MyThread();  // ②
12         Thread thread2 = new MyThread();
13         thread1.start();  // ③
14         thread2.start();
15     }
16 }

5.3 第三种 利用Callable接口、Executors工具类、ExecutorService接口、Future接口实现有返回结果的多线程

① 自定义类实现Callable接口,并覆写call()方法

② 利用Executors创建一个线程池 ExecutorService接口的实例对象

③ 调用线程池的submit()方法来启动线程,该方法会返回一个Future接口的实例对象

④ 调用Future实例对象的get()方法,获得线程的返回结果

 1 package com.test;
 2  
 3 import java.util.concurrent.*;
 4  
 5 class MyThread implements Callable<String>{
     // ① 创建线程
 6      @Override
 7      public String call() {
   
 8          return "Thread body";
 9      }
10 }
11 public class MultiThread3 {
   
12     public static void main(String[] args){
   
13         ExecutorService threadPool = Executors.newSingleThreadExecutor();  // ② 创建线程池
14         Future<String> future = threadPool.submit(new MyThread());  // ③ 想线程池提交任务
15         try {
   
16             System.out.println(future.get());  // ④ 获得return的值
17         } catch (InterruptedException | ExecutionException e) {
   
18             e.printStackTrace();
19         }
20         threadPool.shutdown()  // 关闭线程池
21     }
22 }

Callable接口提供了比Runnable接口更强大的功能

1)Callable接口的call()方法可以在任务结束后提供一个返回值,Runnable接口的run()方法无法提供这个功能

2)Callable接口的call()方法可以抛出异常,Runnable接口的run()方法无法提供这个功能

3)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值