03-24面试整理

这篇博客整理了Java面试中的并发编程知识点,包括线程安全的集合类如ConcurrentHashMap,以及Object类的方法。同时,介绍了Spring框架的相关内容,如Spring IOC的自定义、Bean的生命周期和AOP原理。还探讨了多线程问题,如三个线程循环打印的解决方案,并讲解了数据结构和算法的基础知识,最后提到了Redis的集群方案。
摘要由CSDN通过智能技术生成

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 的一些方法

在这里插入图片描述

从上向下来看

  1. 初始化器 registerNatives 就像Java 的启动类加载器bootstrap classloader。这些都是使用 C C++ 实现的。 联系Java 程序与操作系统

  2. Objetc() 空构造器

  3. 同 1

  4. 所有的Java 对象都可以调用 getClass()方法该 方法 将会 返回 该 对象 所属 类 对应 的 Class 对象。

  5. 每个对象都有一个地址,HashCode()方法 可以获取对象的Hash 值。比如"=="就是使用HashCode() 来比较两个对象

  6. 对象的比较也是使用equals 来比较地址。但String 已经重新其equals 方法,用来比较”值相等“。

  7. clone() 对象克隆。返回对象的一个拷贝——(深克隆 ?浅克隆? )

  8. toString 返回以一 个字符串表示的 Number 对象值。在普通的pojo 中,通常重写toString()方法打印内容。

  9. notify() notifyAll()

    Java 中每个对象都有一把称之为 monitor 监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。

    这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象

    所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。

  10. wait() 方法 让线程进入等待状态,当前线程释放其锁。

    直到其他 对象的notify() notifyAll() 才可以唤醒当前线程。

  11. 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 指的是 对象的作用范围

Scopedescription
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协议进行通信。

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值