Java面试——并发问题(sleep、synchronized、volatile等)

本文深入探讨了Java并发编程中的关键概念,包括线程的六种状态,线程池的核心参数,sleep与wait的区别,lock与synchronized的对比,volatile的线程安全特性,以及乐观锁与悲观锁的运用。同时,还分析了Hashtable与ConcurrentHashMap的线程安全性及性能差异,并详细阐述了ThreadLocal的工作原理及其在解决线程安全问题上的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


并发

线程有哪些状态

在这里插入图片描述
Java线程分成六种状态

  • 新建
  • 可运行
  • 阻塞
  • 等待
  • 等待(有时间)
  • 消亡

在这里插入图片描述

线程池的核心参数

在这里插入图片描述

1.corePoolSize 核心线程数目

  • 最多保留的线程数

2.maximumPoolSize 最大线程数目

  • 核心线程+救急线程

3.keepAliveTime 生存时间

  • 针对救急线程

4.unit 时间单位

  • 针对救急线程

5.workQueue

  • 阻塞队列

6.threadFactory 线程工厂

  • 可以为线程创建时起名字

7.handler 拒绝策略

  • 四种

sleep VS wait

  • 共同点:wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。
  • 方法归属不同
    • sleep方法是Thread的静态方法
    • 而wait(),wait(long)是Object的成员方法,每个对象都有
  • 醒来时间不同
    • 执行sleep(long)和wait(long)的线程都会在等待相同毫秒后醒来
    • wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒就一直等待下去
    • 都可以被打断唤醒
  • 锁特性不同
    • wait方法的调用必须先获取wait对象的锁,而sleep不用
    • wait方法执行后会释放对象锁,允许其他线程获得该对象锁
    • sleep如果在synchronized代码块中执行,并不会释放对象锁

lock 和synchronized的区别

  • 语法层面:
    • synchronized是关键字,源码在jvm,是C++实现
    • lock是接口,由jdk提供,Java实现
    • 使用synchronized时,退出同步代码块时会自动释放锁,lock需要调用unlock手动释放锁
  • 功能层面
    • 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
    • lock提供了很多synchronized不具备的功能,例如获取等待状态、公平锁、可打断、可超速、多条件变量
    • lock有适合不同的场景使用,如ReentrantLock,ReentrantReadWriteLock(读多写少)
  • 性能层面
    • 在没有竞争或较小时,synchronized做了很多优化、如意向锁、轻量级锁,性能不赖
    • 在竞争激烈时,lock的实现通常能提供更好的性能

volatile能否保证线程安全

  • 1.线程安全考虑三个方面:可见性、有序性、原子性
    • 可见性:一个线程对共享变量修改,另一个线程能看到最新的结果
    • 有序性:一个线程内代码按编写顺序执行
    • 原子性:一个线程内多行代码以一个整个执行,期间不能有其他线程的代码插队
  • 2.volatile能保证可见性、有序性,但不保证原子性,在多线程时可能会在执行了一半时切换线程,共享变量交错覆盖
//共享变量 sum = 10
//t1线程内容
{
    int s = sum;
	s -= 5;  //如果在这时线程切换就会结果覆盖了
	sum = s;
    return sum;
}

//t2线程
{
    int s = sum;
    s += 5;
    sum = s;
    return sum;
}
  • 可见性问题:是JIT优化更改了热点代码(常调用的方法,大量循环的代码),可以添加参数-Xint表示只需要翻译成机器码不用jit优化。

乐观锁和悲观锁

  • 1.悲观锁的代表是synchronized和 Lock锁
    • 其核心思想是【线程只有占有了锁,才能去操作共享变量,每次只有一个线程占锁成功,获取锁失败的线程,都得停下来等待】
    • 线程从运行到阻塞、再从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
    • 实际上,线程在获取synchronized和Lock锁时,如果锁已被占用,都会做几次重试操作,减少阻塞的机会
  • 2.乐观锁的代表是AtomicInteger,使用cas(compareAndSet——对比并修改)(一般配合volatile使用)来保证原子性
    • 其核心思想是【无需加锁,每次只有一个线程能成功修改共享变量,其它失败的线程不需要停止,不断重试直至成功】
    • 由于线程一直运行,不需要阻塞,因此不涉及线程上下文切换
    • 它需要多核cpu支持,且线程数不应超过cpu核数

Hashtable_vs_ConcurrentHashMap

  • Hashtable 与ConcurrentHashMap都是线程安全的Map集合

  • Hashtable并发度低,整个Hashtable对应一把锁,同一时刻,只能有一个线程操作它

  • 1.8之前ConcurrentHashMap使用了Segment+数组+链表的结构,每个Segment对应一把锁,如果多个线程访问不同的Segment,则不会冲突

  • 1.8开始ConcurrentHashMap将数组的每个头节点作为锁,如果多个线程访问的头节点不同,则不会冲突

    • 1.7:ConcurrentHashMap底层数据结构是Segment+数组+链表

    • 1.8:ConcurrentHashMap底层数据结构是数组+(链表 | 红黑树)

    • 1.7:ConcurrentHashMap初始化直接创建了16大小的数组

    • 1.8:ConcurrentHashMap是在第一次使用put方法才创建

    • 1.7:ConcurrentHashMap扩容机制是超过3/4扩容

    • 1.8:ConcurrentHashMap扩容机制是满3/4就扩容

  • Hashtable初始数组大小是11、扩容阈值是超过3/4,扩容数量是原容量*2+1

谈一谈对ThreadLocal的理解

  • ThreadLocal可以实现【资源对象】的线程隔离,让每个线程各用各的【资源对象】,避免争用引发的线程安全问题
  • ThreadLocal同时实现了线程内的资源共享
  • 其原理是,每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
    • 调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
    • 调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
    • 调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值