0
点赞
收藏
分享

微信扫一扫

Java并发编程实战3:互斥锁

颜娘娘的碎碎念 2022-05-02 阅读 87

文章目录


前言

“同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。通过互斥来保证原子性。
需要互斥执行的代码称为临界区

加锁本质就是在锁对象的对象头中写入当前线程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 还没来得及发生变化)。所以解决原子性问题,是要保证中间状态对外不可见。

举报

相关推荐

0 条评论