Java基础

1.Hash map的底层实现原理?

数组+链表+红黑树的结构 链表长度为8,默认数组长度为16 ,负载因子0.75 扩容阈值数组长度*0.75

Hashmap的put方法

当我们添加元素时,先判断数组是否为空,如果为空进行扩容操作,不为空,根据key的值计算hash值得到插入的索引(i),如果数组[i]为空,直接创建新的节点插入,如果不为空,判断key的值是否与当前的key相同,如果相同,会进行覆盖当前key的value值

如果没有判断是否为红黑树,如果是在树中插入键值,如果不是,遍历数组[i],如果当前链表长度大于8时,会自动转换成红黑树,插入成功后,判断键值数是否超过阈值,超过进行扩容

Hashmap中的get方法

根据key计算hash值,得到数组的索引,然后遍历链表

Hash冲突?--采用拉链法

当两个key的hashcode相同,那么数组的下标也就相同

为什么要转换成 红黑树?

链表过长性能发生问题,红黑树维护需要时间,占用内存大,所以没有超过不使用

特点:

1)每个节点非红即黑

2)根节点总是黑色的

3)如果节点是红色的,则它的子节点必须是黑色的(反之不一定)

4)每个叶子节点都是黑色的空节点(NIL节点)

5)从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

负载因子为什么要使用0.75?

设置一个临界值方便hashmap的扩容,设置0.75是充分利用了内存空间防止了hash冲突

重写hashcode和equals方法需要注意?

两个对象相等,hashcode一定相等

两个对象不等,hashcode不一定相等

hashcode相等,两个对象不一定相等

hashcode不等,两个对象一定不等

2.Hash map和CurentHashMap的区别?

Hash Map允许键值为空,但ConCurrentHashMap都不允许

一个线程不安全,一个线程安全

1.8之前采用锁分段的计算,将整个数组进行分段,在每一个分段上加入锁进行保护,使用lock锁,比hashtable的锁关键字的粒度更加精细,并发性更好,1.8之后才用cas算法也就是乐观锁的方式进行判断上锁

①联系:HashMap与ConcurrentHashMap都是基于哈希表(数组、链表、红黑树)的存储。

②区别:HashMap是非线程安全的,而ConcurrentHashMap是线程安全的,它在HashMap的基础上加入了分段锁的概念,即创建的Map的初始数组中的每一个元素为一个区块(segment),当线程A对键值对进行操作时只会锁住当前区块,而不影响对其它区块的操作,提高了Map在多线程并发操作时的效率。

3.CurentHashMap的底层实现原理?

currentHashMap采用分段数组+链表实现,线程安全,引入分段锁,多线程情况下性能高

分段锁: 和ConcurrentHashMap相对应的HashTable也是线程安全的,但是HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

1.8之后采用数组+链表+红黑树的结构,使用synchronized关键字+cas算法进行上锁

  1. Hash1.7是基于数组和链表实现的,为什么不用双链表?HashMap1.8中引入红黑树的原因是?为什么要用红黑树而不是平衡二叉树?

链表解决了哈希冲突,使用单链表就能解决,双链表需要更大的存储空间

为了提高hashmap的性能,用于解决hash碰撞后,链表过长导致索引效率慢,通过红黑树来解决快速增删改查的特点

  1. HashMap、HashTable、ConcurrentHashMap的原理与区别?

 1..HashMap和Hashtable都是数组+拉链实现的哈希表,HashSet是key value结构,仅仅是存储不重复的元素,相当于简化版的HashMap

2..两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全

3.HashMap可以使用null作为key,而Hashtable则不允许null作为key

虽说HashMap支持null值作为key,不过建议还是尽量避免这样使用,因为一旦不小心使用了,若因此引发一些问题,排查起来很是费事

4.HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类

5.HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75

6.两者计算hash的方法不同
Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模

HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸

6.synchronized和Lock的区别?

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;

而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronize

d。所以说,在具体使用时要根据适当情况选择。

7.ThreadLocal的原理和实现?

ThreadLoacl--线程封闭

把对象封闭在一个线程中,即使这个对象的线程不安全,他也不会出现并发安全问题。

ThreadLocal是解决线程安全问题,通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。

ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方法更方便地获取该信息的场景。每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念

8.什么是多线程?

进程:

是一个程序运行状态和资源占用的描述(内存/cpu)描述

特点:独立/并发/动态

线程:

是进程的组成部分,一个进程可以有多个线程

并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事

并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

线程的状态:

创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存

就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度

运行(running)状态: 执行run()方法

阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用

死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

线程的创建方式:

1.继承Thread类,并重写run()方法。

1.定义Thread子类,并重写run方法

2.创建它的实例,通过调用start启动线程

2.实现Runnable接口,并重写run()方法。

定义Runnable实现接口,重写run方法

创建实现类的实例,此实例作为target来创建Thread对象

调用start方法启动

3.使用Callable和Future创建线程

4.使用线程池例如用Executor框架

多线程:

多线程可以提高程序的效率。

以般线程之间比较独立,互不影响

一个线程发生问题,一般不影响其它线程

9.为什么要使用线程池?

线程池:是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可

降低线程的资源消耗、提高响应速度、线程的可管理性

线程池的关闭?

shutdown方法--中断工作线程

shoudownNow方法--中断所有控制线程

线程池的参数?

corePoolSize:核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true

maximumPoolSize:线程池允许的最大线程池数量

keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间

unit:超时时间的单位

workQueue:工作队列,保存未执行的Runnable 任务

threadFactory:创建线程的工厂类

handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。

10.线程池的创建方式?

newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;Fixed(固定的)

newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;Cached(缓存)

newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;

newScheduledThreadPool:创建一个可以执行延迟任务的线程池;Scheduled(延迟)

newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;

newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)

ThreadPoolExecutor:最原始的创建线程池的方式,它包含了7个参数可供设置。

线程池的工作原理:

线程池判断核心线程池里的线程是否都在执行任务。 如果不是,创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。

线程池判断阻塞队列是否已满。 如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中。如果阻塞队列已满,则进入下个流程。

线程池判断线程池里的线程是否都处于工作状态。 如果没有,则创建一个新的工作线程来执行任务。如果已满,则交给饱和策略来处理这个任务。

11.主流锁

为什么需要使用锁?

当数据库出现并发事务时,有可能会产生数据不一致,为了保护而使用锁控制

什么是死锁?

多个事务在同一资源相互占用,并锁定对方资源,达成死循环

互斥条件:资源在某个时刻只能被一个进程所占用。(排他性控制)
请求与保持条件:当前进程由于请求资源而阻塞时,对已拥有的资源保持不放。
不剥夺条件:当前进程已经获得的资源在未使用完毕前不能被其他进程强行剥夺,只能自己使用完主动释放。
循环等待条件:若干个进程之间一定会形成一个头尾相连的资源请求环路。

解决方法

按同一顺序访问对象 (避免出现循环)
避免事务中的用户交互 (减少持有资源的时间,较少锁竞争)
保持事务简短并处于一个批处理中 (减少持有资源的时间)
使用较低的隔离级别 (使用较低的隔离级别(例如已提交读)比使用较高的隔离级别(例如可序列化)持有共享锁的时间更短,减少锁竞争)
使用基于行版本控制的隔离级别

线程锁住同步资源的锁?

悲观锁:假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。

乐观锁:假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现

悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。

乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

CAS算法:当且仅当 V(读写的内存值)等于A(进行比较的值)时,CAS通过原子方式用新值B(拟写入的新值)来更新V(读写的内存值)的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

线程锁住同步资源失败的锁,线程需不要阻塞?

自旋锁:如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

多个线程竞争同步资源流程的区别?

无锁:程序不会有锁的竞争。那么这种情况我们不需要加锁,所以这种情况下对象锁状态为无锁。

偏向锁:偏向锁,顾名思义,它会偏向于第一个访问锁的线程

优点:加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。

缺点:线程存在锁竞争,会对额外的锁撤销的消耗

适用场景:适用于只有一个线程访问的同步快场景

轻量级锁:自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

优点:竞争的线程不会阻塞,提高了程序的响应速度。

缺点:如果始终得不到锁竞争的线程使用自选消耗CPU

适用场景:追求响应时间,同步块执行速度高

重量级锁:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。

优点:线程竞争不使用自旋,不会消耗CPU。

缺点:线程阻塞,响应时间缓慢

适用场景:追求吞吐量,同步块执行时间长

多个线程能不能共享一把锁吗?

共享锁:共享锁是指锁可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加独占锁。获得共享锁的线程只能读数据,不能修改数据。

排他锁:指锁一次只能被一个线程所持有。如果一个线程对数据加上排他锁后,那么其他线程不能再对该数据加任何类型的锁。获得独占锁的线程即能读数据又能修改数据。

12.解释方法重写与方法重载

方法重写:子类继承父类时,存在与父类中某一方法相同的方法名,返回类型和参数列表,则子类方法将覆盖父类原有的方法。方法重写特性:方法重写的返回值、方法名、参数列表必须完全一致。

方法重载:在一个类中具有多个函数名相同,参数列表不同的方法。参数列表不同包含参数个数和类型的不同。

13.==和equals()的区别

①关于==解读:

基本数据类型:比较的是值是否相同

引用数据类型:比较的是引用(地址)是否相同

②关于equals解读:

对于重写equals方法的引用数据类型:比较的是值是否相同

对于未重写equals方法的引用数据类型,源码继承object的equals方法,等同于==,即比较引用(地址)是否相同

13.面向对象和面向过程的区别?

面向对象:将数据及对数据的方法放在一起,形成一个相互依存的整体--对象,将同类的对象进行抽取共性形成类。对象就是一个类的实例,有状态和行为。类只是一个模板,用来描述对象的行为和状态

面向过程:就是我们分析好了步骤,按部就班的完成就行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值