一、项目背景与简介
JavaGuide由GitHub用户Snailclimb开发并维护,是一个全面而深入的Java学习资源库。它旨在为Java初学者和有经验的开发者提供一个系统的学习路径和丰富的资源,帮助他们系统地学习和巩固Java及相关技术知识。
二、项目内容与特点
- Java基础:详细解释了Java的基本概念、语法和核心类库,为初学者打下坚实的基础。
- 集合框架:深入分析了ArrayList、LinkedList、HashMap等常用集合类的源码和使用场景,帮助开发者更好地理解和应用这些集合类。
- 并发编程:涵盖了线程、锁、并发集合等高级并发编程知识,帮助开发者掌握Java并发编程的核心技术。
- JVM:详细介绍了Java虚拟机的内存模型、垃圾回收机制和类加载过程,让开发者对Java的运行环境有更深入的了解。
- 新特性:总结了从Java 8到最新版本的各个版本的新特性,帮助开发者紧跟Java技术的最新发展。
此外,JavaGuide还涉及了计算机网络、操作系统、数据结构与算法等计算机科学基础知识,为开发者提供了一个全面的技术栈学习平台。
JavaGuide的特点还包括:
- 全面性:覆盖了Java开发的各个方面,从基础到高级,从理论到实践,应有尽有。
- 实用性:项目中的内容都是经过精心挑选和整理的,旨在解决实际开发中的常见问题。同时,提供了大量的代码示例和面试题,帮助开发者更好地理解和应用知识。
- 更新及时:紧跟Java的最新发展,及时更新内容,确保信息的时效性和准确性。
- 社区支持:JavaGuide是一个开源项目,拥有活跃的社区支持。用户可以在社区中参与贡献、寻求帮助或分享经验。
三、如何学习与使用JavaGuide
- 下载与安装:可以从JavaGuide的官方网站或GitHub仓库下载源代码压缩包,或者通过git命令来clone仓库。下载完成后,进入JavaGuide项目的根目录。
- 构建与运行:确保系统已经安装了Maven等构建工具,然后执行相应的命令来构建并运行JavaGuide。例如,可以使用“mvn clean package java -jar target/JavaGuide-xxx.jar”命令来运行JavaGuide。
- 学习与进阶:无论是Java初学者还是有一定经验的开发者,都可以通过JavaGuide系统地学习和巩固Java及相关技术知识。可以按照JavaGuide的学习路径逐步深入,也可以根据自己的兴趣和需求选择特定的章节进行学习。
- 面试准备:JavaGuide中包含了大量的面试题和解析,非常适合正在准备Java相关职位面试的求职者。可以通过学习这些面试题和解析来提高自己的面试技巧和竞争力。
- 日常开发参考:JavaGuide中的源码分析和最佳实践部分可以作为日常开发中的参考资料。在遇到问题时,可以查阅JavaGuide中的相关内容来寻找解决方案或灵感。
乐观锁和悲观锁详解
如果将悲观锁(Pessimistic Lock)和乐观锁(PessimisticLock 或 OptimisticLock)对应到现实生活中来。悲观锁有点像是一位比较悲观(也可以说是未雨绸缪)的人,总是会假设最坏的情况,避免出现问题。乐观锁有点像是一位比较乐观的人,总是会假设最好的情况,在要出现问题之前快速解决问题。
什么是悲观锁?
悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
像 Java 中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
public void performSynchronisedTask() {synchronized (this) {// 需要同步的操作}
}private Lock lock = new ReentrantLock();
lock.lock();
try {// 需要同步的操作
} finally {lock.unlock();
}
高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题(线程获得锁的顺序不当时),影响代码的正常运行。
什么是乐观锁?
乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。
在 Java 中java.util.concurrent.atomic
包下面的原子变量类(比如AtomicInteger
、LongAdder
)就是使用了乐观锁的一种实现方式 CAS 实现的。
CAS 算法
CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。
原子操作 即最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。
CAS 涉及到三个操作数:
- V:要更新的变量值(Var)
- E:预期值(Expected)
- N:拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。
举一个简单的例子:线程 A 要修改变量 i 的值为 6,i 原值为 1(V = 1,E=1,N=6,假设不存在 ABA 问题)。
- i 与 1 进行比较,如果相等, 则说明没被其他线程修改,可以被设置为 6 。
- i 与 1 进行比较,如果不相等,则说明被其他线程修改,当前线程放弃更新,CAS 操作失败。
当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。
关于 CAS 的进一步介绍,可以阅读读者写的这篇文章:CAS 详解,其中详细提到了 Java 中 CAS 的实现以及 CAS 存在的一些问题。
一、JMM的基本概念
JMM定义了线程之间共享变量的可见性和有序性规则,为开发者提供了一种可靠的同步机制,以避免并发程序中常见的线程安全问题。JMM包含两个主要的内存区域:主内存和工作内存。
- 主内存:主内存是所有线程共享的内存区域,包含了程序的全局变量和静态变量。主内存是多个线程之间的交互媒介,线程之间通过主内存进行数据的传递和共享。
- 工作内存:工作内存是线程私有的内存区域,包含了线程栈中的局部变量和操作线程栈的操作数栈等。每个线程都有自己独立的工作内存,工作内存存储了线程在执行过程中需要用到的数据。
二、JMM的三大特性
JMM的三大特性是原子性、可见性和有序性,它们共同保证了多线程环境下数据的安全性和一致性。
-
原子性(Atomicity):
- 原子性指的是一个操作要么全部执行成功,要么全部不执行。JMM保证了对基本类型的读写操作的原子性。例如,对一个int类型的变量进行++操作,JMM保证这个操作不会出现读取脏数据或者写入不完整数据的情况。
- JMM通过使用synchronized关键字、volatile关键字和原子类(如AtomicInteger)等机制来保证原子性。
-
可见性(Visibility):
- 可见性指的是一个线程对一个变量的写操作对其他线程可见。即使在不同的线程中,一个线程对共享变量的修改也能被其他线程立即观察到。
- JMM通过使用锁机制和内存屏障来实现可见性。例如,使用synchronized关键字对代码块进行同步,每次进入同步块的线程都会从主内存中读取最新的值,保证了可见性。volatile关键字也可以确保被修饰的变量的写操作对其他线程立即可见。
-
有序性(Ordering):
- 有序性指的是在一个线程中的操作顺序与程序代码的顺序一致。然而,在多线程环境下,由于指令重排和缓存一致性等原因,程序的执行顺序可能与代码顺序不一致。
- JMM通过使用内存屏障来禁止特定类型的指令重排,保证程序的有序性。例如,volatile关键字可以禁止对volatile变量的读/写操作进行指令重排序。
三、JMM的Happens-Before规则
Happens-Before规则是JMM的核心原则之一,用于定义操作之间的顺序关系,基于此关系来保证数据的可见性和一致性。以下是Java中的Happens-Before规则:
- 程序顺序规则:在一个线程内,代码的执行顺序按照程序代码的书写顺序。
- 监视器锁规则:一个unlock操作在后续对同一锁的lock操作之前。
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对该volatile变量的读操作。
- 线程启动规则:Thread.start()方法先行发生于此线程的每一个动作。
- 线程中断规则:对线程的interrupt()调用先行发生于被中断线程的代码检测到中断事件。
- 线程终止规则:线程的所有操作先行发生于此线程的终止检测(Thread.join()返回,Thread.isAlive()返回false)。
- 对象终结规则:对象的构造函数执行结束先行发生于finalize()方法的开始。
四、JMM的应用与注意事项
- 正确使用volatile关键字:当某个变量在多线程环境下被频繁读取且不需要复杂的同步操作时,可以使用volatile关键字来保证变量的可见性和有序性。
- 合理选择synchronized和Lock:在需要对共享资源进行原子操作且需要控制访问顺序时,可以使用synchronized关键字或Lock接口来保证原子性和可见性。
- 注意指令重排序的影响:编译器和处理器可能会对指令进行重排序以提高性能,但在多线程环境下,指令重排序可能导致内存可见性问题。因此,在编写多线程程序时,需要特别注意指令重排序的影响,并合理使用内存屏障等机制来保证程序的正确性。