Java面试题总结

1,线程中,并行和并发的区别

这两都是描述处理多个任务的方式

并发:某个时间段内处理多个任务的思想,这些任务不是真正的同一时刻的任务执行,而是通过cpu的快速切换,使得宏观(逻辑上)看起来是同时执行的。

如:单核处理器,在不同的时间片上轮流执行多个程序。

并发的目的是提高系统的响应效率和cpu资源的利用率。

并行:同一时刻,多条指令在多核cpu上同时执行,属于真正的物理上的同时。

2,cookie和session以及token的区别

cookie:

数据由服务器端生成通过HTTP协议发送给客户端浏览器,存储在浏览器端,并在每次发送请求时自动携带该cookie,让服务器读取其中的信息。

特点:

存储量小:cookie大小通常受到浏览器的限制,一般不超过4KB,

客户端每次发送请求时,会自动附带cookie数据。

安全风险:cookie中的数据可以被用户或其他网站访问到,不适合存储敏感信息。(可能被篡改)

方便JavaScript交换数据,方便获取用户信息,浏览器可能会禁用cookie。

使用场景:使用cookie来标识用户会话,实现用户的登录状态管理。(帮助客户端和服务器端状态连接)

seesion:

数据存储在服务器端,在客户端浏览器中仅保存一个与session相关的标识(session ID)

安全性高:session的数据存在服务器端,客户端不可见,因此适合存储敏感信息

应用场景:用户身份认证,存储用户信息,实现身份认证和权限管理。

缺点:占用服务器资源;扩展性差;需要依赖cookie跨域限制。集群下,前后端分离时,cookie进行传递时很麻烦的,所以session用来鉴权就不适用了。

token:通过json来传递的密钥字符串

区别:

 特性        Cookie                        Session                     Token                      
 存储位置    客户端(浏览器)              服务器端                    客户端(LocalStorage/Cookie) 
 用途       

 会话管理、用户偏好、

行为跟踪 

 会话管理、身份验证、

授权   

 身份验证、授权             
 安全性     

 易被客户端访问,需设置 

HttpOnly 和 Secure 属性 

 相对安全,但 

Session ID 需要保护 

 存储在客户端,需通过 HTTPS 传输 
 跨域        复杂,需要服务器端配置        复杂,需要服务器端共享      简单,客户端携带 Token 即可 
 服务器负担  无                            增加服务器内存负担          无                         
 过期机制    可设置过期时间                可设置过期时间              可设置过期时间             

数据容量:Cookie的容量较小,一般不超过4KB;而Session可以存储更多的数据(可以保存对象),相对于cookie来说session的存储容量更大,无限制。

安全性:由于Cookie存储在客户端,其中的数据可被用户和其他网站访问,因此安全性较低;而Session数据存储在服务器端,对客户端不可见,因此相对较安全。

传输方式:Cookie通过HTTP协议自动发送给服务器,每次请求都会携带Cookie数据;而Session可以通过Cookie或URL重写的方式传递Session ID。

生命周期:Cookie可以通过设置过期时间来指定存储的时间,可以是短期的或长期的;而Session默认情况下会持续到用户关闭浏览器或会话超时。

应用场景:Cookie适合存储少量的数据,常用于用户身份认证、记住登录状态等场景;Session适合存储较大的数据,常用于购物车功能、跨页面数据传递等场景。

3,String和StringBuffer和StringBuilder的区别

String : 是不可变对象,每次对类型进行改变时,会生成一个新的String对象,然后将指针指向新的String对象。

安全性:StringBuffer 是多线程安全的,JDK1.0出现,方法都被同步了(被synchronized修饰)保证了数据的一致性和完整性。由于要获取锁,性能上有一定损耗。

StringBuilder 多线程不安全 ,JDK5.0出现,是一个可变的字符序列

(底层一个动态扩容的字符数组)

使用策略:

基本原则:操作少量数据用String;单线程操作大量数据,用StringBuilder;多线程操作大量数据,用StringBuffer。

多线程下共享变量且需要保证结果的情况下用StringBuffer。其他情况使用StringBuider。

4,Sleep函数中的特殊用法sleep(0)

sleep函数被用来使程序暂停执行一段时间,这段时间可以是毫秒,秒等,这个函数的调用通常是为了避免程序执行过快或等待任务完成。

sleep(0):目的不是为了让程序真正休眠,而是为了让当前线程放弃时间片,让其他等待的任务有机会执行,在多线程,多任务环境可以提高程序效率。(避免线程饥饿,提示调度器给其他线程一个机会)

sleep(1):短暂休眠后又再次参与CPU的竞争。是为了在执行之间添加短暂延迟的场景或等待异步操作完成的

使用场景:

降低cpu使用率,如果有一个线程在忙等待(busy-waiting)某件事情的发生时,可在循环体中插入sleep(1)来减少此循环对cpu资源的占用

定时任务:在简单定时任务或需要延迟操作时,不需要精确的定时控制,可实现短暂延迟。

5,sleep()和yield() 方法的不同之处:

都用于让出cpu时间片

sleep会让线程处于阻塞/休眠状态,随后立即被唤醒进入就绪状态。(也就是说会进行一次状态切换)

yield是直接让此线程再次进入就绪状态,参与与其他同优先级的线程的cpu资源竞争,不需要设置参数,此方法属于提示性方法,无强制性。

主要差异

调度行为:

sleep(0)依赖操作系统的调度策略,可能允许低优先级线程运行。yield()仅仅只是给调度器一个提示,只让出CPU给相同或高优先级线程运行,不过有被忽视的可能,结果不定。

    适用场景:对于大多数应用场景来说,直接控制线程调度并不是推荐的做法,更好的方式可能是通过合理的线程池配置、任务划分以及同步机制来管理并发。因此,除非是在进行非常精细的性能调优或者了解具体的调度行为对应用有直接影响的情况下,一般不会频繁使用 sleep(0) 或 yield()。

6,wait和sleep的区别?
        1,所属类不同
  • wait属于Object类,
  • sleep属于Thread类。
        2, 用法不同
                   3,锁的持有
  • wait方法可以加参数,也可以不加时间参数,且需要配合synchronized使用,
  • sleep必须设置参数时间。
  • 调用wait方法会释放当前线程持有的锁,
  • sleep休眠了,但时锁没释放。
       4, 线程进入状态
  • sleep方法调用后,线程状态是TIMED_WAITING,有限等待状态,
  • 调用无参wait方法会进行无线等待,状态是WAITING。
7,volatile 关键字了解?
  • 一个声明变量的关键字,所有线程在这个变量被修改后立即看到这个变量的最新值(可见性)
  • 会阻止编译器和处理器在代码优化时进行指令重排,确保程序的执行顺序(有序性)
  • 不保证原子性,原子性需要通过同步机制(如互斥锁)或硬件支持的原子操作指令来实现,在 Java 和 C# 中,提供了专门的原子类(如   AtomicInteger  、  Interlocked   等)来实现原子操作。

8,CAS概念

CAS(Compare-And-Swap,比较并交换)是一种重要的原子操作,在多线程编程中用于实现无锁的数据结构和算法

V:(var) 变量

E:预期值

N:新值

线程首先从V中读取到当前值,再根据业务逻辑基于V得到一个新值,

执行CAS操作:使用CAS操作尝试更新内存位置V,线程提供三个参数给CAS操作:内存位置V、刚刚读取到的值作为预期值A、以及计算得到的新值B。(JUC包下的Unsefe类大量用的这个)

验证更新。

需要注意的是,当多个线程同时尝试对同一变量进行CAS操作时,可能会出现“ABA问题”,即变量的值从A变为B再变回A,导致CAS操作误判。针对这个问题,可以采用版本号或者时间戳等方式来解决

9,内存泄漏和内存溢出

溢出:是可用的内存不满足当前程序需要的内存

泄漏:程序运行过程中分配的内存,在使用完毕的时候没有得到正确释放,导致占用内存越来越多,可用内存越来越少直到耗尽,典型的案例就是(ThreadLocal没有释放内存)。

10,ThreadLocal

底层数据结构:ThreadLocalMap(定制化的哈希表)

用于存储线程本地变量,ThreadlocalMap是Thread类的一个内部类,每个线程都有一个独立的ThreadLocalMap实例

哈希表的组成:ThreadLocalMap 内部维护了一个数组 (Entry[] table),用于存储键值对,每个 Entry 是一个弱引用(WeakReference)对象,表示一个键值对

哈希冲突解决

    1,ThreadLocalMap 使用开放地址法(Open Addressing)来解决哈希冲突。
    2,当发生冲突时,会通过线性探测法(Linear Probing)寻找下一个空闲位置。
    3,这种方式相比链表法(如 HashMap)更加高效,因为 ThreadLocalMap 的键通常是固定的 ThreadLocal 对象,数量相对较少。

初始容量和扩容

    1,初始容量为 16(即 INITIAL_CAPACITY),且容量总是 2 的幂次方。
    2,当数组中存储的元素数量超过一定阈值(threshold,通常是容量的 2/3)时,会触发扩容操作。
   3, 扩容时,数组大小会翻倍,并重新计算每个键值对的位置。

内存泄漏问题

虽然 ThreadLocalMap 的键是弱引用,但值仍然是强引用。如果 ThreadLocal 对象被垃圾回收后,对应的值没有及时清理,可能会导致内存泄漏。因此,建议在使用完 ThreadLocal 后,显式调用 remove() 方法清理资源。

使用场景:
典型场景:

    用户会话信息:在 Web 应用中,用户的会话信息(如用户 ID、登录状态等)可以通过 ThreadLocal 存储,避免每次方法调用时显式传递。
    请求上下文:在一次 HTTP 请求处理过程中,可以将与该请求相关的上下文信息(如事务 ID、请求参数等)存储在 ThreadLocal 中,供多个方法共享。

2. 数据库连接管理

在传统的 JDBC 操作中,数据库连接对象通常是线程不安全的。为了避免多个线程共享同一个连接导致的问题,可以使用 ThreadLocal 为每个线程分配独立的数据库连接。

3.事务管理

在分布式事务或数据库事务中,事务的状态(如是否开启事务、事务的隔离级别等)通常需要绑定到当前线程。ThreadLocal 可以用来存储事务上下文,确保每个线程的事务状态独立。

4.安全的日期格式化

在 Java 中,SimpleDateFormat 是非线程安全的。如果多个线程共享同一个 SimpleDateFormat 实例,可能会导致数据混乱。通过 ThreadLocal,可以为每个线程分配独立的 SimpleDateFormat 实例。

5.日志追踪

在分布式系统中,为了方便调试和排查问题,通常会给每个请求分配一个唯一的事务 ID 或跟踪 ID。这个 ID 可以通过 ThreadLocal 存储,并在日志中打印出来,从而实现请求链路的追踪。

6. 线程池中的任务隔离

在使用线程池时,由于线程会被复用,可能导致不同任务之间的数据污染。通过 ThreadLocal,可以确保每个任务的数据独立。

11,count(1),count(*)和count(列名)的区别

计算满特定条件的行数

count(*)/count(1):计算表中所有行数,包括null.

理论上两者在性能上是等价的,大部分DBMS会优化这两种查询以达到相同,count(1)只能算是习惯。

count(列名):会计算列中的非null值,会有一个null值的判断,性能上比上面的会差一些。

12,什么是双亲委派机制

首先,此机制是JAVA类加载器的一种工作模式,确保了类加载的安全性和一致性。

解释:双亲委派机制是,当一个类加载器收到类加载的请求时,会将请求进行上抛,即让父类进行处理,直到到达顶级即启动类加载器(BootStrap ClassLoader),如果父类加载器无法加载该类(即在它的搜索范围内找不到所需的类),子类加载器才会尝试自己加载。

为什么要这样?

  • 保证核心类库的安全性
  • 避免类的重复加载
  • 维持程序的一致性:确保不同类加载器加载的相同名称的类实际上是同一个类

JVM中有三种默认的类加载器:启动类加载器(Bootstrap ClassLoader),扩展类加载器(Extension ClassLoader),应用程序类加载器(Application ClassLoader)。它们之间形成了一种层次结构,其中启动类加载器位于最顶层,而应用程序类加载器处于最底层。

13,方法中的局部变量会产生并发问题吗

不会,局部变量的作用域仅限于方法内部,他们存储在线程的栈内存,每个线程都有自己独立的栈空间,因此不同的线程之间的局部变量的相互隔离的。

14,synchronized 可以用于哪些方法,锁定范围是什么?
1.实例方法上

锁定的是当前实例(调用该方法的对象),如果两个线程通过不同实例对象来访问这个同步代码块的话,不会被阻塞。

作用范围:整个方法体,锁定的是当前实例(this)。

2.静态方法上

锁定的是这个类的所有实例共享的Class对象,当前对象的所有实例如果要执行这个静态方法,同一时刻只能有一个线程进入。

作用范围:整个方法体,锁定的是类的Class对象。

3.代码块上

应用于代码块,指定一个特定的对象作为锁。这种方式提供了更细粒度的控制,允许你仅同步需要保护的部分代码,而不是整个方法

使用当前实例作为锁:

public void someMethod() {
    synchronized (this) {
        // 同步代码块
    }
}

使用某个特定对象作为锁

private final Object lock = new Object();

public void someMethod() {
    synchronized (lock) {
        // 同步代码块
    }
}

使用类级别的锁(类似于同步静态方法):

public void someMethod() {
    synchronized (MyClass.class) {
        // 同步代码块
    }
}

作用范围:仅限于指定代码块,锁定的是给定的对象

15,双重检查锁DCL对象半初始化问题?

双重检查锁(Double-Checked Locking Pattern, DCL)是一种用于实现延迟初始化(Lazy Initialization)的线程安全模式,尤其在单例模式中常见

对象半初始化问题的根本原因是指令重排序,可能导致线程看到未完全初始化的对象。
    解决方法包括:
        使用 volatile 关键字禁止指令重排序。
        使用静态内部类实现单例模式。
        使用 enum 实现单例模式(推荐)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值