2021 0324 上午10:30
面试整理
关于线程安全的集合类
-
早期的线程安全集合
-
Vector是 Java 早期提供的线程安全的动态数组。 Vector 内部使用对象数组来保存数据数据,可扩容。
不过因为效率较低,现在已经不太建议使用
-
Hashtable 是一个线程安全的 Map 实现, 支持多线程。
-
Stack 堆栈类 FILO(first In Last Out)
-
enumeration 枚举接口
-
-
Collections 包装类
现在我们使用 ArrayList 来替代Vector 使用HashMap 来替代HashTable。但它们不是线程安全的,所以Collections工具类中提供了相应的包装方法把它们包装成线程安全的集合
List<E> synArrayList = Collections.synchronizedList(new ArrayList<E>()); Set<E> synHashSet = Collections.synchronizedSet(new HashSet<E>()); Map<K,V> synHashMap = Collections.synchronizedMap(new HashMap<K,V>());
通过使用锁对象的方式来实现同步
-
JUC(java.util .concurrent)
-
ConcurrentHashMap 在HashMap 的基础上进行了优化
在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响
JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率
-
CopyOnWrite
- 在“写”的时候,不是直接“写”源数据,而是把数据拷贝一份进行修改,再通过悲观锁或者乐观锁的方式写回。
- CopyOnWriteArrayList
- CopyOnWriteArraySet
-
ConcurrentLinkedQueue
-
ConcurrentLinkedDeque
-
ConcurrentSkipListSet
-
ConcurrentSkipListMap
-
Object类的方法有哪些
打开JDK 的源码,看看Java 底层的Object 的一些方法
从上向下来看
初始化器 registerNatives 就像Java 的启动类加载器bootstrap classloader。这些都是使用 C C++ 实现的。 联系Java 程序与操作系统
Objetc() 空构造器
同 1
所有的Java 对象都可以调用 getClass()方法该 方法 将会 返回 该 对象 所属 类 对应 的 Class 对象。
每个对象都有一个地址,HashCode()方法 可以获取对象的Hash 值。比如"=="就是使用HashCode() 来比较两个对象
对象的比较也是使用equals 来比较地址。但String 已经重新其equals 方法,用来比较”值相等“。
clone() 对象克隆。返回对象的一个拷贝——(深克隆 ?浅克隆? )
toString 返回以一 个字符串表示的 Number 对象值。在普通的pojo 中,通常重写toString()方法打印内容。
notify() notifyAll()
Java 中每个对象都有一把称之为 monitor 监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。
这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象
所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。
wait() 方法 让线程进入等待状态,当前线程释放其锁。
直到其他 对象的notify() notifyAll() 才可以唤醒当前线程。
finilize() 如图 已过期 经常与final、finally拿来一起比较。
finilize()的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。
三个线程循环打印问题
之前就做过,直接贴代码演示
三个线程之间不断的进行wait() 和notify() 通知与被通知
package nio.buffer;
public class PrintABCUsingWaitNotify {
private int times; // 打印的次数
private int state = 0; // 打印的状态
private Object objectA = new Object();
private Object objectB = new Object();
private Object objectC = new Object();
public PrintABCUsingWaitNotify(int times) {
this.times = times;
}
//A 方法的执行 锁住A 对象
public void printA(){
print("AA", 0, objectA, objectB);
}
public void printB(){
print("BB", 1, objectB, objectC);
}
public void printC(){
print("CCC", 2, objectC, objectA);
}
//传入的要打印的值 状态 锁住当前线程 通知下一个线程执行
public void print(String name, int targetState, Object curr, Object next){
for (int i = 0; i < times;) {
// 锁住当前打印的对象
synchronized (curr) {
// 以余数 的方式来存储
while(state % 3 != targetState){
try {
curr.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
state++;
System.out.print(name);
synchronized (next) {
next.notify();
}
}
}
}
// main 方法中使得其循环打印10次
public static void main(String[] args) {
PrintABCUsingWaitNotify p = new PrintABCUsingWaitNotify(10);
// thread A启动
new Thread(new Runnable() {
@Override
public void run() {
p.printA();
}
}).start();
// thread B启动
new Thread(new Runnable() {
@Override
public void run() {
p.printB();
}
}).start();
// thread c启动
new Thread(new Runnable() {
@Override
public void run() {
p.printC();
}
}).start();
}
}
// 最后的执行结果
// AABBCCCAABBCCCAABBCCCAABBCCCAABBCCCAABBCCCAABBCCCAABBCCCAABBCCCAABBCCC
// 循环打印10次AABBCCC
Main线程(主线程)等待的问题
假设一个主线程要等待3个 Worker 线程执行完才能退出,可以使用CountDownLatch来实现
这些都属于JUC 包中的同步工具类:
Semaphore
CountDownLatch
CyclicBarrier
Exchanger
Phaser
countDownLatch就是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
方法有
await() // 当前线程等待
countDown() // 计数器 - 1
如何自定义Spring IOC
首先来解释 什么是Spring IOC。
IOC(Inverse of Control )。控制反转。在Java 中,意味着我们将设计好的对象交付给容器来进行控制,而不是在对象的内部直接进行控制。
DI(dependency injection)。依赖注入,由容器动态的将某个依赖关系注入到组件之中。
Spring 框架的核心就是Spring 容器。使用这个容器来创建对象,将它们装配在一起,并配置它们并管理它们的完整生命周期。
那么问题来了,如何自定义一个IOC容器呢?
比如我们需要一个courseDao(课程的数据交互层[data access Object]), 我们不需要手动创建,而是从Spring IOC 容器中来获得。
Spring 中的 IoC 的实现原理就是工厂模式加反射机制,从而解决(service层 和dao 层的代码)耦合度太高的问题。
将创建的对象信息保存在配置文件(beans.xml),之后编写一个BeanFactory 工具类
public class BeanFactory {
private static Map<String, Object> ioc = new HashMap<>();
// 程序启动时,初始化对象实例
static {
try {
// 1.读取配置文件
InputStream in =
BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 2.解析xml
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(in);
// 3.编写xpath表达式
String xpath = "//bean";
// 4.获取所有的bean标签
List<Element> list = document.selectNodes(xpath);
// 5.遍历并创建对象实例,设置到map集合中
for (Element element : list) {
String id = element.attributeValue("id");
String className = element.attributeValue("class");
Object object = Class.forName(className).newInstance();
ioc.put(id, object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取指定id的对象实例, 获得该实例对象
public static Object getBean(String beandId) {
return ioc.get(beandId);
}
}
Spring Bean 的生命周期
- 容器启动
- 实例化Bean
- Spring 使用依赖 注入填充所有属性
- Bean 指定的初始化( init() )方法的执行
- ~~ 前置处理BeanPostProcessor
- preProcessBeforeInitialization() 方法
- 自定义的实用的方法(utility method)
- ~~后置处理BeanPostProcessor
- postProcessAfterInitialization() 方法
- destroy() 随着容器的关闭,对象的销毁。
Spring AOP
相关通知类型
前置通知 <aop:before >
后置通知 < aop:afterRunning>
异常通知 < aop:afterThrowing >
最终通知 <aop: after>
环绕通知 <aop: around>
AService Bservice Cservice 三个业务类调用同一个Dao接口, 这个对象是同一个对象吗?
@Autowired
private CourseMapper courseMapper;
Bean 标签的作用范围
<bean id="" class="" scope=""></bean>
id: Bean 实例对象在Spring容器中的唯一标识
class:Bean的全限定名
scope 指的是 对象的作用范围
Scope | description |
---|---|
singleton | 单例 |
prototype | 多例 |
request | 将Bean对象存入到request域中 |
session | 将Bean对象存入到session域中 |
这个要根据配置来说~~
常用的数据结构
-
一维
- 数组
- 链表
- 栈
- 队列
-
二维
-
二叉树
- 完全二叉
- 满二叉树
- 二叉搜索树
- BFS DFS
- 红黑树
- B B+ 树
-
图(邻接矩阵 邻接表 社交关系)
-
算法
- 排序
- o(n^2) 冒泡 插入 选择
- o(n logn ) 快排 归并
- o(n) 桶排序 计数排序 基数排序
- 堆排序(大顶堆 小顶堆)
思想
- 递归
- 贪心
- 回溯(八皇后)
- 动态规划
算法
一个有序数组,比如{2, 3, 4, 6, 7}。 其中缺失一个元素,如何找出来这个元素的值?
-
通过从前向后遍历一遍,使用 (i++) 的值与前一个元素+1 的值进行比较。
这样做的时间复杂度为O(n),平均从前向后一半的元素。
-
二分法: 从两端向中间开始二分,因为是有序数组,所以两端的中间值与索引的中间值进行比较,之后查找错误的索引位置,找到此缺失的元素。
如果此数组是无序的,如何将其排序?
回顾概念 桶排序 (Bucket Sort)将排序的数据分到几个有序的桶内。
如果要排序的数据有 n 个,我们把它们均匀地划分到 m 个桶内,每个桶里就有 k=n/m 个元素。
每个桶内部使用快速排序,时间复杂度为 O(k * logk)。
m 个桶排序的时间复杂度就是 O(m * k * logk),因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))。
当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 O(n)。
Redis集群方案
- 主从配置
- 哨兵模式
- redis 集群
主从复制:
一主一从,一主多从
作用:
读写分离
数据容灾
从节点配置replicaof [主节点ip]: [主节点端口号]
哨兵模式
由一个或多个sentinel实例组成sentinel集群可以监视一个或多个主服务器和多个从服务器。
当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证redis的高可用性。
Redis 集群模式
将数据进行分区(部署在不同主机上)
根据分区键(id) 进行分区。
hash 分区
SNOWFLAKE 雪花算法
搭建部署:
- 修改各个服务器的端口号(redis.conf)。启动redis 集群。
集群通信:redis cluster节点间采取gossip协议进行通信。