目录
在JVM中,synchronized
关键字是一个非常重要的同步机制,用于确保在多线程环境下,同一时刻只有一个线程可以执行某个方法或代码块。以下是关于JVM中synchronized
的详细解释:
一、synchronized的作用
synchronized
关键字的主要作用是防止多个线程同时访问共享资源而导致的数据不一致性和竞态条件问题。通过使用synchronized
,可以确保在同一时间内只有一个线程能够进入同步代码块或同步方法,从而保护共享资源不被并发修改。
二、synchronized的用法
Java中的每一个对象都可以作为锁。具体表现 为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
1、普通同步方法
对于普通同步方法,锁是当前实例对象。这意味着当一个线程进入该同步方法时,其他线程无法访问同一个实例对象上的其他同步方法,直到当前线程退出该方法。
public class Counter {
private int count = 0;
// 普通同步方法,锁是当前实例对象
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在上面的例子中,increment
方法和getCount
方法都被synchronized
修饰,因此它们是同步方法。当一个线程调用increment
方法时,它会锁定Counter
实例对象,其他线程无法同时调用同一个Counter
实例上的getCount
方法或其他同步方法。
2、静态同步方法
对于静态同步方法,锁是当前类的Class
对象。这意味着当一个线程进入该静态同步方法时,其他线程无法访问该类的其他静态同步方法,直到当前线程退出该方法。
public class StaticCounter {
private static int count = 0;
// 静态同步方法,锁是当前类的Class对象
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
在上面的例子中,increment
方法和getCount
方法都是静态同步方法,因此它们锁定的是StaticCounter
类的Class
对象。当一个线程调用increment
方法时,它会锁定StaticCounter
类,其他线程无法同时调用StaticCounter
类的其他静态同步方法。
3、同步方法块
对于同步方法块,锁是synchronized
括号里配置的对象。这意味着锁定的对象可以是任何对象,而不仅仅是当前实例对象或类的Class
对象。
public class SynchronizedBlockExample {
private int count = 0;
private final Object lock = new Object(); // 显式锁对象
// 同步方法块,锁是lock对象
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
在上面的例子中,increment
方法和getCount
方法都包含同步方法块,它们锁定的是同一个显式锁对象lock
。当一个线程调用increment
方法时,它会锁定lock
对象,其他线程无法同时调用同一个SynchronizedBlockExample
实例上的getCount
方法或其他使用相同锁对象的同步方法块。
三、锁的介绍与升级
锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状 态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。
1、无锁状态
无锁状态意味着没有对任何资源进行锁定,所有线程都能访问并修改资源。无锁状态的实现通常依赖于CAS(Compare and Swap)操作,这是一种硬件级别的原子操作,用于在并发编程中实现无锁算法。
无锁状态就像是一个开放的图书馆,任何人都可以自由进出并借阅书籍,无需任何限制或等待。这里的“书籍”可以理解为共享资源,而“借阅”则代表对资源的访问和操作。
特点:性能最高,因为没有任何同步机制的开销。
2、偏向锁状态
偏向锁是一种针对单线程访问同步块的优化。它会在第一次使用锁的时候,将对象头中的标记位设为偏向锁,并记录下持有锁的线程ID。在之后的访问中,如果发现是同一线程持有锁,则无需进行加锁和解锁的操作,从而提高了程序的运行效率。
偏向锁状态就像是一个为特定读者(线程)预留的VIP阅读室。只要这个读者(线程)再次来访,他就可以直接进入阅读室,无需任何等待或手续。
特点:适用于线程竞争不激烈、单线程多次获取锁的场景。能够减少不必要的同步操作,降低锁获取和释放的开销。
升级条件:当有其他线程尝试获取这个已被偏向的锁时,原有的偏向锁需要被撤销,并升级为轻量级锁或重量级锁。
3、轻量级锁状态
轻量级锁是一种用于对线程访问同步块的优化。它的特点是在访问同步块之前,先将对象头中的标记位设为轻量级锁,并将锁的持有线程ID记录在对象头中。当另一个线程也想访问同步块时,会发现对象头中的标记位已经是轻量级锁,此时它会使用CAS操作来尝试获取锁。
轻量级锁状态就像是一个需要排队进入的阅览室。当有人(线程)想要进入时,他会先查看门口是否有空位(锁是否可用),如果有,他就可以直接进入;如果没有,他就会在门口等待(自旋),直到有空位为止。
特点:适用于线程竞争不激烈、持有锁的时间不长的场景。通过自旋等待锁释放,避免了线程阻塞和上下文切换的开销。
升级条件:如果自旋等待的时间过长,或者CAS操作失败次数过多,轻量级锁会升级为重量级锁。
4、重量级锁状态
重量级锁是一种针对多线程访问同步块的优化。它的特点是在访问同步块之前,先将对象头中的标记位设为重量级锁,并在操作系统层面上进行加锁和解锁的操作。
重量级锁状态就像是一个需要严格管理和排队进入的会议室。当有人(线程)想要进入时,他必须先向管理员(操作系统)申请,并等待管理员的批准和安排。如果会议室已经满员(锁已被其他线程持有),他就会被阻塞在门外,直到有空位为止。
特点:能够保证多线程程序的数据一致性,避免了数据竞争、死锁等问题。但性能开销较大,因为涉及到操作系统级别的线程调度和上下文切换。
升级条件:通常由轻量级锁升级而来,当轻量级锁无法满足同步需求时,会升级为重量级锁。