synchronized
是Java语言提供的一个内置锁机制,用于实现线程间的同步。它的工作原理基于Java对象监视器(Monitor)机制,具体如下:
-
监视器(Monitor): 每个Java对象都有一个相关的监视器锁(monitor lock),也称为内置锁或互斥锁。当线程试图访问一个由
synchronized
修饰的方法或代码块时,它必须首先获取相关对象的监视器锁。 -
锁定过程:
- 对于
synchronized
修饰的方法,锁住的是当前对象实例(即this
); - 对于
synchronized
修饰的代码块,可以指定任意对象作为锁; - 静态
synchronized
方法锁住的是类的Class对象。
当线程执行
synchronized
代码块或方法时,它会尝试获取监视器锁。如果锁是自由的(即没有其他线程持有该锁),线程将获取锁并继续执行。如果锁已经被其他线程持有,则当前线程会进入阻塞等待状态,直到持有锁的线程释放该锁。 - 对于
-
原子性和可见性:
synchronized
确保了在同一时间只有一个线程可以访问被保护的代码区域,从而实现了线程间的互斥性,保证了操作的原子性。此外,它还提供了内存可见性保证,即当一个线程退出synchronized
代码块时,对共享变量的修改对其他线程立即可见。 -
锁释放: 当线程执行完
synchronized
代码块或方法后,会自动释放监视器锁,使得其他等待的线程有机会获取锁并执行相应的代码。 -
异常处理: 即使在
synchronized
代码块或方法中抛出了异常,监视器锁也会在异常退出时被释放,以确保其他线程能够获得锁。
在现实生活中的一个例子可以帮助我们更好地理解synchronized
关键字的工作原理。想象一下在一家银行的ATM机房里有多台ATM机器,每台机器都配备了一个保险箱来存放现金。这里的保险箱就好比是Java对象的监视器锁(monitor lock)。
同步方法的例子: 假定每个客户(线程)在取款时必须遵守这样的规则:一次只能有一个客户使用一台ATM机。这就类似于synchronized
修饰的方法,当一个客户开始取款操作(执行同步方法)时,他会独占使用那台ATM机(获取锁)。在此期间,其他客户必须等待(线程被阻塞)。一旦这位客户完成取款并离开(方法执行结束,释放锁),下一位客户才能开始使用这台ATM机。
同步代码块的例子: 进一步细化,设想银行的保险箱不仅用于存取现金,还有一个单独的抽屉用于存放银行卡。这时,我们可以把整台ATM机看作是synchronized
修饰的方法,而抽屉则作为一个单独的同步代码块。当客户A正在取现金时,客户B可以过来插卡查询余额(这两个操作分别对应同步方法和同步代码块,使用不同的锁),因为他们使用的是保险箱的不同部分。但如果客户C也要取现金,他就必须等待客户A完成取款操作。
总结起来,synchronized
的关键字就像银行服务中的排队系统和锁机制,确保了在多用户(多线程)环境下,对公共资源(如ATM机、保险箱或抽屉)的访问是有序且互斥的,从而避免了并发操作导致的数据不一致等问题。
同步方法:
public class Counter {
private int count = 0;
// 同步方法,确保任何时候只有一个线程可以访问increment方法
public synchronized void increment() {
count++;
}
// 同步方法读取count值,确保在读取时不被其他线程干扰
public synchronized int getCount() {
return count;
}
}
同步代码块
public class SharedResource {
private final Object lock = new Object();
private int sharedValue = 0;
public void modifySharedValue() {
// 同步代码块,使用特定的对象作为锁
synchronized (lock) {
sharedValue++;
// 执行其他同步操作...
}
}
}