Skip to content

Java并发基础

Java包

1744805343985

线程与进程

Java 线程和操作系统的线程有啥区别?

Java SE最常用的JVM是Oracle/Sun研发的HotSpot VM。在这个JVM的较新版本所支持的所有平台上(除了Solaris之外),它都是使用1:1线程模型的, 也就是说Java线程本质就是操作系统的线程, 一个Java线程对应一个系统内核线程

1744805350295

线程的生命周期

NEW: 初始状态,线程被创建出来但没有被调用 start()

RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。

BLOCKED:阻塞状态,需要等待锁释放。

WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。

TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。

TERMINATED:终止状态,表示该线程已经运行完毕。

1744805358042

并发与并行

  • 并发:两个及两个以上的作业在同一 时间段 内执行。
  • 并行:两个及两个以上的作业在同一 时刻 执行。

wait与sleep方法的区别

参考: https://www.cnblogs.com/loren-Yang/p/7538482.html

  • sleep方法属于Thread类中方法, 表示让当前线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态. 一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。
  • wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。
  • wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)

补充两个重要的方法:yield()和join()

  • yield方法

暂停当前正在执行的线程对象。

yield()方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。如果没有的话,那么yield()方法将不会起作用,并且由可执行状态后马上又被执行。

  • join方法

join方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。

锁思想与锁技术

锁思想特点适用场景对应的锁技术
悲观锁假设冲突会发生,先加锁写操作多或并发冲突概率高的场景synchronized、ReentrantLock
乐观锁假设冲突很少发生,更新时检查冲突读操作多或并发冲突概率低的场景AtomicInteger、AtomicReference、CAS
读写锁读锁共享,写锁独占读多写少的场景ReentrantReadWriteLock
自旋锁获取锁失败时不断重试锁占用时间短的场景AtomicInteger、AtomicReference、CAS
分段锁将数据分段,每段独立加锁高并发场景,数据可分段处理ConcurrentHashMap(Java 7 及之前版本)
偏向锁假设锁总是由同一线程获取单线程重复获取锁的场景JVM 内置的锁优化机制
轻量级锁通过 CAS 尝试获取锁,失败则升级为重量级锁低并发场景JVM 内置的锁优化机制
重量级锁通过操作系统互斥量实现,线程阻塞高并发场景synchronized(竞争激烈时)

锁优化

指导思想实现方式优点
减少锁的粒度分段锁、拆分为多个小锁减少锁竞争,提高并发性能
减少锁的持有时间缩小锁范围、使用局部变量或副本减少锁竞争,提高并发性能
使用无锁编程原子类、无锁数据结构避免锁开销,提高并发性能
读写分离ReentrantReadWriteLock、StampedLock提高读操作的并发性能
锁消除JVM 逃逸分析减少不必要的锁开销
锁粗化JVM 自动优化减少锁的获取和释放次数
偏向锁和轻量级锁JVM 锁优化机制在低竞争情况下减少锁开销
使用非阻塞算法原子类、无锁队列避免锁开销,提高并发性能

synchronized关键字与ReentrantLock区别

  • synchronized 是依赖于 JVM 实现的,线程试图获取锁也就是获取 **对象监视器 **monitor 的持有权。
  • ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成)。

相比 synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点:

  • 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
  • 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而 synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的 ReentrantLock(boolean fair)构造方法来制定是否是公平的。
  • 可实现选择性通知(锁可以绑定多个条件) : synchronized关键字与 wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于 Condition接口与 newCondition()方法。

volatile 关键字的作用(变量可见性、禁止重排序)

volatile 变量具备两种特性, volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同的 CPUcache 中。而声明变量是 volatile 的, JVM 保证了每次读变量都从内存中读,跳过 CPU cache这一步。

特性:

  • 变量可见性
  • 禁止重排序
  • 比sychronized更轻量级的同步锁

CAS操作与ABA问题

  • CAS 是一种无锁编程技术,用于实现多线程环境下的原子操作。它的核心思想是:
  1. 比较 :检查某个内存位置的值是否等于预期值。
  2. 交换 :如果相等,则将该内存位置的值更新为新值;否则,不做任何操作。
  • ABA 问题 :CAS 只能检查值是否相等,无法感知值的变化过程
  • 解决 ABA 问题的方法
    • 版本号机制: 在值的基础上增加一个版本号,每次修改值的同时更新版本号, CAS 操作同时检查值和版本号
    • Java 提供了 AtomicStampedReference 类,通过版本号解决 ABA 问题

ThreadLocal

  • 原理:

Thread类中含有ThreadLocalMap类型的变量,而ThreadLocalMap是ThreadLocal的静态内部类, Map的key是ThreadLocal对象, value是对应的值;ThreadLocal可以看做是ThreadLocalMap的封装,传递了变量值,当当前线程调用ThreadLocal进行get, set方法时, 就会创建或者往ThreadLocalMap中添加键值对。即,实际的数据存储在Thread的map变量中,因此线程安全。

  • 内存泄漏

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()get()remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用 remove()方法

线程池

  • 4种线程池

    线程池类型特点适用场景
    FixedThreadPool线程数量固定,任务队列无界负载较重的服务器环境
    CachedThreadPool线程数量动态调整,空闲线程超时回收大量短生命周期的异步任务
    SingleThreadExecutor单线程执行,保证任务顺序性需要顺序执行任务的场景
    ScheduledThreadPool支持定时任务和周期性任务需要定时执行任务的场景
  • 线程池优点:

    • 降低资源消耗 。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    • 提高响应速度 。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
    • 提高线程的可管理性 。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。