文章目录
前言
“同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。通过互斥来保证原子性。
需要互斥执行的代码称为临界区
加锁本质就是在锁对象的对象头中写入当前线程id,但是new object每次在内存中都是新对象,所以加锁无效。
一、Java 语言提供的锁技术:synchronized
class X {
// 修饰非静态方法
synchronized void foo() {
// 临界区
}
// 修饰静态方法
synchronized static void bar() {
// 临界区
}
// 修饰代码块
Object obj = new Object();
void baz() {
synchronized(obj) {
// 临界区
}
}
}
Java 的一条隐式规则:
当修饰静态方法的时候,锁定的是当前类的 Class 对象,在上面的例子中就是 Class X;
当修饰非静态方法的时候,锁定的是当前实例对象 this。
一把锁可以保护多个资源,但是一个资源只能有一把锁来保护
class SafeCalc {
static long value = 0L;
synchronized long get() {
return value;
}//方法加锁,锁的是this
synchronized static void addOne() {
value += 1;
}//静态方法加锁,锁类(SafeCalc)
}
锁的是变量value,但是两把锁,所以错误
二、保护多个资源
1.保护多个没有关联关系的资源
用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁还有个名字,叫细粒度锁。
class Account {
// 锁:保护账户余额
private final Object balLock
= new Object();
// 账户余额
private Integer balance;
// 锁:保护账户密码
private final Object pwLock
= new Object();
// 账户密码
private String password;
// 取款
void withdraw(Integer amt) {
synchronized(balLock) {
if (this.balance > amt){
this.balance -= amt;
}
}
}
// 查看余额
Integer getBalance() {
synchronized(balLock) {
return balance;
}
}
// 更改密码
void updatePassword(String pw){
synchronized(pwLock) {
this.password = pw;
}
}
// 查看密码
String getPassword() {
synchronized(pwLock) {
return password;
}
}
}
账户余额和修改密码由两个锁来实现
2.保护有关联关系的多个资源
class Account {
private Object lock;
private int balance;
private Account();
// 创建 Account 时传入同一个 lock 对象
public Account(Object lock) {
this.lock = lock;
}
// 转账
void transfer(Account target, int amt){
// 此处检查所有对象共享的锁
synchronized(lock) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
案例实际不可行,所有转账都是串行化执行
总结
“原子性”的本质是什么?其实不是不可分割,不可分割只是外在表现,其本质是多个资源间有一致性的要求,操作的中间状态对外不可见。例如,在 32 位的机器上写 long 型变量有中间状态(只写了 64 位中的 32 位),在银行转账的操作中也有中间状态(账户 A 减少了 100,账户 B 还没来得及发生变化)。所以解决原子性问题,是要保证中间状态对外不可见。