操作系统
1.Q: 在操作系统中,什么时候会发生用户态到内核态的切换
A: 操作系统中,用户态和内核态是两种不同的权限级别,他们对应着不同的执行环境和执行权限。用户态事指程序在一般的运行情况下的的级别,它具有别较低的权限级别,只能访问受限的资源。而内核态是操作系统内核运行的特权级别,它具有更高的的权限,可以直接访问系统的所有资源。区分这两个状态目的是为了保证系统的安全性,一些敏感的操作只能允许在内核态下面进行执行。比如说修改寄存器中的一些数据
用户态到内核态的切换通常发生在以下情况下面
1. 系统调用:当用户程序需要执行特权操作,如打开文件、创建进程等,它需要通过系统调用来请求操作系统内核提供相应的服务。这时,用户态的程序会触发一个特定的指令,将控制权转移到内核态,执行相应的内核代码来满足用户程序的请求。完成后,内核会将控制权返回给用户程序,使其继续执行。(简单来说就是执行一些敏感操作的时候,将线程的控制权交给操作系统,让操作系统代为完成)
2. 异常和中断:当发生异常或中断事件时,例如除零错误、内存访问违规、硬件设备的中断请求等,处理器会暂停当前正在执行的用户态程序,并转移到内核态执行相应的异常处理程序或中断服务程序。这种切换是由硬件中断机制触发的,目的是保证操作系统能够及时响应和处理异常情况。
3. 特权指令的执行:某些特定的指令只能在内核态下执行,例如修改全局控制寄存器、访问硬件设备的特权寄存器等。当用户态程序执行这些特权指令时,会触发异常,导致切换到内核态执行相应的内核代码。
总之,用户态到内核态的切换是在用户程序需要访问特权资源或请求操作系统内核服务时发生的,通过系统调用、异常和中断以及特权指令的执行等方式实现。这种切换的目的是保护系统的安全性和稳定性,确保只有经过授权的代码才能执行特权操作。
计算机网络
1. GET方法和POST方法的区别
- 参数的位置不同,前者直接把参数放到url,后者放到请求体里
- 参数的长度不同:url的长度是受限的,
- 发送的数据包的个数
- 是否进行缓存
- 用途不同
- 是否幂等
Java基础
1. 浅拷贝和引用拷贝的区别
浅拷贝 | 引用拷贝 | |
---|---|---|
定义 | 浅拷贝创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址,也就是说,浅拷贝仅仅复制了引用类型的引用地址,而不是复制引用类型本身。因此,新旧对象共享这部分的内存,修改其中一个对象的引用类型属性,会影响到另一个对象。 | 引用拷贝并不创建对象的一个新实例,而是仅仅复制了原有对象的引用地址。这意味着新的引用实际上指向的是同一个对象,任何通过新引用对对象的修改都会影响到原对象,因为它们实质上是同一个对象的两个标签 |
有没有创建新的对象 | 有,会堆中创建一个新的对象 | 不会,直接复制原对象的引用地址 |
对属性的修改是否会引起拷贝对象内容的而改变 | 可能会,如果是引用类型的属性,会引起蝴蝶效应,如果是基本类型就不会 | 会 |
内存共享 | 基本类型属性独立,而引用类型属性则与原对象共享内存 | 引用拷贝完全共享原对象的内存,包括所有属性和方法 |
2. equal()和==的区别
对于==
- 对于基本数据类型来说,
==
比较的是值。 - 对于引用数据类型来说,
==
比较的是对象的内存地址。
对于equals()方法
equals()
不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等
首先equals方法是Object类默认有的,所有的对象都继承自Object类,所以所有的对象都有equals()方法
- 如果对象没有重写equals()方法,那么使用equals() 的作用就和 == 相同
- 如果重写了equals()方法,那么具体的作用就要看重写的代码的逻辑
3. Integer i1 =1; Integer i2 = 1; i1 == i2的结果是?
true
虽然i1和i2是两个引用类型,不是两个基本数据类型,理论上来说直接比较对象的引用地址的话,结果应该是false才对,但是在Java中,对于Integer
对象,当数值在一个特定的范围内(通常是-128到127之间),Java为了提高效率和减少对象创建,使用了享元模式(Flyweight Pattern),即在这个范围内的Integer
对象会被缓存起来,重用已有的对象。所以比较的结果是true,假如Integer i1 =128; Integer i2 = 128; i1 == i2的结果就是false
4. 堆和栈的区别
堆是java虚拟机中最大的一块内存,对象的new()实例化创建都是在堆中的进行的,
栈是一种自动分配和释放内存的方式。它用于存储局部变量和方法调用的上下文信息。当方法执行结束或变量超出作用域时,栈上的数据会自动释放。栈用于存储方法调用的上下文信息和局部变量。每个线程都有自己的栈,栈上的数据只能在创建它的方法内部访问
对象引用都在 栈 中,指向的对象实例存储在堆中,堆中的对象实例是可以被共享的,栈是每个线程专属的
5. 接口和抽象类的共同点和区别
区别
接口 | 抽象类 | |
成员变量 | 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值 | 可以由 public, private, protected修饰,可以在子类中被修改 |
方法的构造 |
|
6.HashMap和HashTable的区别
- 线程安全性:前者不安全,后者安全,后者使用了一把重锁来对整个结构进行上锁
- 存储效率:前者高于后者
- 初始容量和扩容机制:前者初始大小是16,后面每次扩2倍,当数组的长度达到64之后,且链表长度大于8的时候,会将链表转化为红黑树;后者的初始大小为11,后面扩容方式是2*n + 1
- 哈希方式,前者使用了高低位混合扰动+hashcode的方式来减少碰撞的概率,后者直接使用hashcode的方式
- 前者的key和value都可以是null,后者不可以
7.HashTable和ConcurrentHashMap的区别
- ConcurrentHashMap在jdk1.8版本做了升级,升级之前是一种分段锁+数组+链表的方式,最多就分16段,所以说它的最大并发数就是16;在jdk1.8之后,取消了分段锁,直接在Node数组层面加锁(这个锁是CAS + synchronized,乐观锁是多个进程不断重试来争取锁,重量级锁是争取到锁的进程),Node数组的大小是16-64,也就是最大并发数得到了提升,其他的扩容机制也是跟hashMap一样,当数组长度大于8的时候,转化为红黑树
- HashTable,就是上锁的时候,直接将整体全部锁住,所以并发度并不高
Java并发编程
1. volatile关键字如何保证变量的可用性
通过每次读取数据的时候 看到这个变量都知道这个变量是共享而且是不稳定的,然后会选择都去公共主存中去读取这个变量,还能禁止进行指令重排序,这个在实现双重校验锁实现对象单例(线程安全)的时候会用到
2. synchronized 关键字底层原理是什么
它是jvm层面来实现的,比如说当synchronized修饰的是一段代码片段的时候,在该文件编译成class字节码文件的时候,会在这段代码段的执行首尾 插入 monitorenter 和 monitorexit 指令
在执行monitorenter
时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。
如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
3 Java线程池的使用
3.1 使用线程池的好处
- 方便统一的组织和管理
- 节省线程创建和销毁的资源开销
- 提高使用线程的速度,免去了线程创建的时间开销
3.2 常用的参数
核心线程数:当队列未达到队列容量时候,最大可以同时运行的线程数量
最大线程数:当队列达到队列容量时候,当前可同时运行的线程数变为最大线程数
队列的类型和各自的使用场景:
- LinkedBlockingQueue无界队列(容量为Integer.MAX_VALUE):FixedThreadPool(核心线程数和最大线程数一致)和SingleThreadExector(核心线程数和最大线程数都是1), 二者的任务队列永远不会被填满,适合一些资源紧张需要限制线程使用量的场景,也适合执行一些稳定的任务,比如说一个特定任务的执行时间和执行频率都是已知且固定的,可以有效地控制线程数量,避免因线程数量过多或过少而导致资源浪费或任务堆积。
- SynchronousQueue同步队列:CachedThreadPool, 本同步队列本身没有容量,不存储元素,来一个任务,有空闲线程就用,没有的话就创一个新的,没有上限,相当于 Integer.MAX_VALUE,可能会创建大量线程导致OOM,适合一些执行时间短,但是频率高的场景,一位内线程池可以根据任务多少自行调节线程数,提高任务的执行效率
- DelayedWorkQueue延迟阻塞队列:任务都Delayed接口,每个任务都与需要设置一个延迟时间,队列内部类似是一个堆的接口,会根据每个任务的延迟时间进行排序,确保最近需要执行的任务在队头,它还是一个阻塞队列,当线程池为空的时候,取不到数据,该线程会被阻塞。比较适合执行定时任务
拒绝策略:
- 抛出异常,拒绝新任务的处理
- 直接抛弃该任务,
- 将该任务打回去,将任务回退给调用者,使用调用者的线程来执行任务
3.3 线程创建的过程
- 判断当前线程池中的线程数量有没有达到核心线程数,如果没到达,直接创建一个新的线程来执行该任务,如果到达了,转至步骤2
- 判断有阻塞队列有没有满,没满的话,直接插入到阻塞队列里面,满了的话,转至3
- 判断当前线程池中的线程数量有没有达到最大线程数,没达到的话,直接创建新的线程来执行该任务,达到了话,转至4
- 执行相应的拒绝策略
4. JMM(java内存模型是什么)
JMM和java并发编程相关,抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须要存储到主存中,规定了从Java源代码到CPU可执行指令的这个转化过程要遵循那些和并发相关的原则和规范,其主要目的就是为了简化多线程编程,增强程序的可移植性。
5. 并发编程的三大重要特性
6. synchronized锁相关
6.1 使用方式
修饰类
修饰对象
修饰代码块
6.2 锁升级的过程
6.3
7. AQS是什么