Java 多线程同步 锁机制与synchronized深入解析
在Java多线程编程中,为了保证线程安全,我们需要使用同步机制来避免多个线程同时访问共享资源造成数据不一致等问题。其中最常用的同步机制就是锁机制。
锁机制
锁机制就是控制多个线程访问共享资源的方式,一般来说,对于共享资源的访问,我们需要通过获取锁来限制只有一个线程可以访问,其他线程需要等待当前线程释放锁后才能访问。Java中锁主要分为两种,分别是对象锁和类锁。
对象锁
对象锁就是使用synchronized关键字修饰的方法和代码块,进入synchronized代码块前需要先获取锁,其他线程需要等待锁的释放后才能进入代码块。例如:
public synchronized void method() {
// 操作共享资源的代码
}
在上面的代码中,synchronized修饰了method()方法,这时method()方法就成为了一个同步方法,其他线程想要执行该方法,先需获取该方法所属对象的锁。如果另一个线程在获取锁之前进入了该方法,则需要等待前一个线程执行完并释放锁后才能执行。
除了synchronized修饰方法外,我们还可以使用synchronized关键字修饰代码块。例如:
public void method() {
synchronized(this) {
// 操作共享资源的代码
}
}
在上面的代码中,synchronized修饰了代码块,this表示当前对象锁,进入代码块前需要先获取锁,其他线程需要等待锁的释放后才能进入。
类锁
类锁就是对类的锁定,在Java中我们可以使用synchronized来实现类锁,示例如下:
public static synchronized void method() {
// 操作共享资源的代码
}
在上面的代码中,synchronized修饰了静态方法,此时该方法就成为了一个类锁。其他线程想要执行该方法,需要获取该类的锁,如果在获取锁之前有其他线程访问该方法,需要等待获取锁。
synchronized深入解析
synchronized是Java中最常用的同步机制之一,它保证了同一时刻只有一个线程可以执行同步代码块或同步方法。从底层实现来看,synchronized分为偏向锁、轻量级锁、重量级锁三种,下面我们来深入了解一下。
偏向锁
偏向锁是一种优化锁机制的手段,它是从JDK 1.6开始引入的。偏向锁的目标是减少获得锁的代价,也就是当同步代码块执行时,只需要一次CAS操作就可以让线程获得锁,减少了获取锁和释放锁的相关操作。
偏向锁是指一种针对加锁操作的优化模式,Java中使用了轻量级锁和重量级锁,其中轻量级锁占用的资源更少,但是切换到重量级锁的时候需要更多的资源,偏向锁则是针对锁竞争不激烈的情况下提供的优化模式,也是轻量级锁和重量级锁之前的过度状态。
轻量级锁
轻量级锁是解决竞争不激烈情况下的线程同步方式,轻量级锁使用CAS操作来实现,CAS操作是一种乐观锁思路,假定竞争是不激烈的,当有线程竞争时,CAS会重试直到成功为止。
轻量级锁的使用流程如下:
-
判断对象头是否为轻量级锁,如果是,执行2,否则执行4。
-
使用CAS操作尝试将对象头里的线程ID改为当前线程ID,如果成功,线程进入临界区执行同步操作,如果失败,说明有其他线程竞争,执行3.
-
如果CAS操作失败,则说明竞争激烈,此时使用自旋锁进行等待,等待一定的时间后如果还不能获得锁,线程会自动膨胀为重量级锁。
-
如果对象头不是轻量级锁,则锁膨胀为重量级锁。
重量级锁
当多个线程竞争同一把锁时,轻量级锁无法提供足够的保障,此时就会升级为重量级锁,重量级锁是基于操作系统内核来实现的,与操作系统内核的Mutex锁类似,重量级锁会切换线程上下文,代价非常高昂,所以尽量避免使用。
示例说明
下面通过两个具体的示例,详细说明synchronized锁机制以及上述所提到的偏向锁、轻量级锁和重量级锁的实际运用和效果。
示例1:并发访问HashMap导致程序异常
在开发过程中,我们可能需要同时操作一个HashMap,这时多个线程可能会同时访问同一个Hash桶,导致数据异常,例如:
public class HashMapTest {
private HashMap<String, String> hashMap = new HashMap<>();
public void testHashMap() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
hashMap.put(UUID.randomUUID().toString(), "");
}, String.valueOf(i)).start();
}
}
}
在上面的代码中,我们创建了10个线程并发地向一个HashMap中添加元素,由于HashMap非线程安全,所以在多线程并发操作下,可能会发生数据不一致等问题。
为了解决这个问题,我们可以使用synchronized来保证线程安全,例如:
public class HashMapTest {
private HashMap<String, String> hashMap = new HashMap<>();
public synchronized void testHashMap() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
hashMap.put(UUID.randomUUID().toString(), "");
}, String.valueOf(i)).start();
}
}
}
在上面的代码中,我们将testHashMap()方法加上synchronized关键字,这样可以保证在同一时刻只有一个线程能够执行该方法,从而保证并发访问HashMap时不会发生数据异常等问题。
示例2:测试锁机制的实际效果
为了测试锁机制的实际效果,我们可以使用JMH进行单线程和多线程的压测,例如:
public class SynchronizedTest {
private static Object lockObject = new Object();
@Benchmark
@Threads(1)
public void testSyncSingleThread() {
synchronized (lockObject) {
// 临界区代码
}
}
@Benchmark
@Threads(4)
public void testSyncMultiThread() {
synchronized (lockObject) {
// 临界区代码
}
}
}
在上面的代码中,我们使用JMH来测试单线程和多线程下synchronized锁机制的性能。
结果显示,在多线程情况下,加锁能够明显提高程序的性能,可以有效避免多个线程并发访问同一个共享资源的问题。同时,在峰值时,锁的效果也最为明显。
总之,synchronized是Java中最常用的同步机制之一,它通过锁机制来控制多个线程对共享资源的访问,保证同一时刻只有一个线程可以执行同步代码块或同步方法。对于锁机制的性能问题,我们可以使用JMH进行压测来调优。在实际运用中,我们需要针对具体场景来选择合适的锁机制,以达到最优的性能表现。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java 多线程同步 锁机制与synchronized深入解析 - Python技术站