Java并发基础
Java包
线程与进程
Java 线程和操作系统的线程有啥区别?
Java SE最常用的JVM是Oracle/Sun研发的HotSpot VM。在这个JVM的较新版本所支持的所有平台上(除了Solaris之外),它都是使用1:1线程模型的, 也就是说Java线程本质就是操作系统的线程, 一个Java线程对应一个系统内核线程
线程的生命周期
NEW: 初始状态,线程被创建出来但没有被调用 start()
。
RUNNABLE: 运行状态,线程被调用了 start()
等待运行的状态。
BLOCKED:阻塞状态,需要等待锁释放。
WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
TERMINATED:终止状态,表示该线程已经运行完毕。
并发与并行
- 并发:两个及两个以上的作业在同一 时间段 内执行。
- 并行:两个及两个以上的作业在同一 时刻 执行。
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 语句块来完成)。
相比 synchronized
,ReentrantLock
增加了一些高级功能。主要来说主要有三点:
- 等待可中断 :
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 是一种无锁编程技术,用于实现多线程环境下的原子操作。它的核心思想是:
- 比较 :检查某个内存位置的值是否等于预期值。
- 交换 :如果相等,则将该内存位置的值更新为新值;否则,不做任何操作。
- 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 支持定时任务和周期性任务 需要定时执行任务的场景 线程池优点:
- 降低资源消耗 。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度 。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性 。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。