《Java零基础教学》是一套深入浅出的 Java 编程入门教程。全套教程从Java基础语法开始,适合初学者快速入门,同时也从实例的角度进行了深入浅出的讲解,让初学者能够更好地理解Java编程思想和应用。
本教程内容包括数据类型与运算、流程控制、数组、函数、面向对象基础、字符串、集合、异常处理、IO 流及多线程等 Java 编程基础知识,并提供丰富的实例和练习,帮助读者巩固所学知识。本教程不仅适合初学者学习,也适合已经掌握一定 Java 基础的读者进行查漏补缺。
🍂1. 前言
在多线程编程中,锁是一个非常重要的话题。Java原生提供了synchronized关键字和Lock接口等方式来实现锁。而JVM在处理锁时,也做了不少优化。本文将阐述JVM对Java的原生锁做了哪些优化。
🍂2. 摘要
本文首先简单介绍了Java中锁的两种实现方式:synchronized和Lock接口。然后分别从JVM级别进行解析:synchronized的优化主要包括:偏向锁、轻量级锁和重量级锁等;而Lock接口的优化主要包括:公平锁和非公平锁等。本文最后给出了相应的代码示例和测试用例,最终得出结论:JVM对Java的原生锁做了很多优化,这些优化大大提升了锁的性能和效率。
🍂3. 正文
🌲3.1 Java中锁的实现方式
在Java中,锁主要有两种实现方式:synchronized和Lock接口。
🍁3.1.1 synchronized
synchronized关键字是Java中最基本的锁实现方式。它是在JVM层面实现的,具有自动释放锁、重入锁和可见性等特性,使用简单方便。
🍁3.1.2 Lock接口
Lock是JDK提供的高级锁机制,它的实现方式是基于Java中的AQS(AbstractQueuedSynchronizer)类实现的。AQS是一个同步框架,它可以用于构建同步器,其中包括ReentrantLock和ReentrantReadWriteLock等。
🌲3.2 synchronized的优化
JVM对synchronized锁进行了很多优化,主要包括以下三种:
🍁3.2.1 偏向锁
偏向锁:这种优化是JVM为了减少同步无竞争情况下的性能消耗而引入的。当一个线程访问同步块并获取锁时,JVM会在对象头和线程栈上做一个记录,记录下线程ID、对象头指针和锁标志。当这个线程持有锁的时间超过一定阈值时,JVM会将锁升级为轻量级锁;当线程竞争锁时,锁会直接升级为重量级锁。
🍁3.2.2 轻量级锁
轻量级锁:这种优化是为了在低竞争条件下,减少锁的开销而引入的。当一个线程请求锁时,JVM会优先使用CAS(Compare And Swap)操作将对象头指针指向线程栈上的锁记录,如果CAS操作成功,则表示该线程获取到了锁,并且锁的状态改变为轻量级锁状态;如果CAS操作失败,则表示有其他线程竞争此锁,线程会进入自旋,不需要系统调用,因此效率极高。
🍁3.2.3 重量级锁
重量级锁:这种优化是为了在高竞争条件下,保证线程竞争公平而引入的。如果一个线程尝试获取锁时,发现锁已经被其他线程持有,它会陷入阻塞状态,直到锁被释放。
🌲3.3 Lock接口的优化
Lock接口相比于synchronized关键字,优化点更多,主要包括以下两种:
🍁3.3.1 公平锁
公平锁:在多个线程竞争同一个锁时,如果锁是公平锁,则每个线程都会按照它们发出请求的时间顺序来获取锁,这种情况下线程获取锁的顺序是有序的。
🍁3.3.2 非公平锁
非公平锁:在多个线程竞争同一个锁时,如果锁是非公平锁,则不保证线程获取锁的顺序,这样可能会导致某些线程一直无法获取到锁。
🌲3.4 代码示例和测试用例
下面给出了对于synchronized和Lock接口的相应代码示例和测试用例:
🍁3.4.1 synchronized实现的锁机制
示例代码如下:
java
public class SyncJava {
//初始为0
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SyncJava sync = new SyncJava();
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executor.execute(() -> {
sync.increment();
sync.decrement();
});
}
//关闭线程池
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println(sync.getCount());
}
}
测试用例说明:创建一个SyncJava实例,实现了increment()方法和decrement()方法,其中使用了synchronized关键字保证线程安全。创建一个线程池,启动1000个线程,每个线程执行一次increment()方法和一次decrement()方法。最终输出count的值,究竟该count值会是多少呢?咱们拭目以待。
实际运行截图如下:
由于synchronized关键字保证了线程安全,最终count值只会跟初始值一样,为 0,实际上输出结果也跟预期结果是一致的。
🍁3.4.2 Lock接口实现的锁机制
示例代码如下:
java
public class LockTest {
private Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
LockTest demo = new LockTest();
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executor.execute(() -> {
demo.increment();
demo.decrement();
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println(demo.getCount());
}
}
测试用例说明:创建一个LockTest实例,实现了increment()方法和decrement()方法,其中使用了Lock接口来实现线程安全。与上述测试保持一致,也是创建一个线程池,启动1000个线程,每个线程执行一次increment()方法和一次decrement()方法,最终测试输出count的值。
实际运行截图如下: 很明显,结果为0,这就验证了多线程中安全执行increment()和decrement()方法,每个线程都会按照它们发出请求的时间顺序来获取锁,哪怕执行一千次一万次,count值也是顺序一加一减,最后count值只能为0.
🍂4. 结论
总的来说,JVM对Java的原生锁进行了很多优化,主要包括偏向锁、轻量级锁、重量级锁、公平锁和非公平锁等。这些优化大大提升了锁的性能和效率。在实际编程中,我们需要了解Java中不同的锁实现方式,并根据实际场景选择最适合的锁。
🍂最后
大家如果觉得看了本文有帮助的话,麻烦给个三连(点赞、分享、转发)支持一下哈。