Synchronized的介绍及其用法
synchronized
关键字用于实现对象级别的同步,它可以保证多个线程在访问某个对象时的互斥性,避免并发访问导致的数据竞争和不一致
public class BankAccount {
private BigDecimal balance;
public BankAccount(String initialValue) {
this.balance = new BigDecimal(initialValue);
}
/**
* 存款
*/
public synchronized void deposit(String amount) {
BigDecimal depositAmount = new BigDecimal(amount);
balance = balance.add(depositAmount);
}
/**
* 取款
*/
public synchronized void withdraw(String amount) {
BigDecimal withdraw = new BigDecimal(amount);
if (balance.compareTo(withdraw) >= 0) {
balance = balance.subtract(withdraw);
} else {
throw new ServiceException("余额不足");
}
}
/**
* 查询余额
*/
public synchronized String getBalance() {
return balance.toString();
}
}
@Test
void synchronizedMethod() {
Assertions.assertDoesNotThrow(() -> {
// 初始化一个0元余额的银行账户
BankAccount bankAccount = new BankAccount("0");
// 创建多个线程执行存款、取款
Thread t1 = new Thread(() -> {
for (int i = 0; i < 500000; i++) {
// 存款
bankAccount.deposit(String.valueOf(1));
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 500000; i++) {
// 取款
bankAccount.withdraw(String.valueOf(1));
}
});
t1.start();
// 阻塞主线程,使得t1线程执行完毕才会被主线程竞争到CPU
try {
t1.join();
} catch (InterruptedException e) {
log.error("InterruptedException, Cause by", e);
}
t2.start();
// 阻塞主线程,使得t2线程执行完毕才会被主线程竞争到CPU
try {
t2.join();
} catch (InterruptedException e) {
log.error("InterruptedException, Cause by", e);
}
/*
直接执行,无异常,输出Balance: 0
t1执行完毕,账户余额50万;t2执行完毕,账户余额0;主线程执行
若将阻塞主线程的代码都注释,输出Balance: 0 && 抛出异常提示:余额不足
主线程优先执行完,输出的余额是初始化的余额;后续t1、t2竞争CPU资源执行各自代码
注释第二次阻塞主线程代码,无异常,输出Balance: 500000
t1线程执行完毕;主线程执行完毕;最后执行t2线程
注释第一次阻塞主线程代码,抛出异常提示:余额不足 && 输出Balance: 1624
t2线程执行+抛出异常+结束;主线程竞争到CPU执行完毕;t1线程执行顺序不确定(可能最开始执行了,可能在t2和主线程之间执行了,可能在最后也执行了)
*/
log.info("Balance: " + bankAccount.getBalance());
});
}
以下是一些关于synchronized的观点:
-
锁粒度较大:使用synchronized关键字时,通常需要锁定整个方法或代码块,这可能会降低并发性能。对于细粒度的锁需求,可以考虑使用更灵活的锁机制,例如ReentrantLock等。
-
可能导致死锁:如果对于多个资源或锁的请求没有正确处理,就有可能导致死锁情况的发生。避免死锁需要谨慎地设计和管理锁的使用。
-
可能导致性能问题:由于synchronized是独占锁,可能会导致线程竞争和等待的情况,进而影响程序的执行效率。在高并发场景下,可能需要考虑使用更高级的并发工具,如ConcurrentHashMap或并发集合类来提高性能。
-
更多选择:随着Java并发编程领域的不断发展,出现了许多更高级的并发工具和框架,例如AQS(AbstractQueuedSynchronizer)、java.util.concurrent包中的工具类等,这些工具提供了更多的灵活性和功能,使得开发者能够更好地处理并发编程中的各种问题。