一、线程相关
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)
BIO
让我们先从BIO,即阻塞IO(blocking I/O)说起:
通常网络I/O在建立链接后,数据的传输通常包括俩个阶段
- 阶段一:等待数据传输到本服务器;
- 阶段二:内核将接受到的数据由"内核缓冲区"复制到“应用缓冲区”;
对应阻塞IO而言,这两个阶段又被分为了4个步骤:
- 用户的应用线程调用recvfrom,开始读取数据,进入阻塞状态;
- 系统内核等待接收网络传输的数据;(阶段一)
- 系统内核将传输的数据由内核缓冲区拷贝到应用缓冲区;(阶段二)
- 应用进程从阻塞中恢复,读取应用缓冲区的数据;
备注:
- “阻塞”可以理解为线程被内核“挂起”,让出CPU使用权,并等待被“唤醒”,重新获取CPU使用权;
- recvfrom()为Unix函数,类似于在JAVA编程中,我们使用read()来读取网络传输的数据;
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)