第二部分:并发工具类14->Lock和condition(上)

1.并发领域核心问题互斥

同一时刻只能一个线程访问共享资源。
Lock解决互斥问题

2.并发领取核心问题同步

线程之间如何通信,协作
Condition解决同步问题

3.为什么要重复造轮子

synchronized已经实现管程,为什么要再实现一遍管程

synchronized性能不好
synchronized在死锁方面,无法破坏不可抢占条件,synchronized申请不到,线程直接阻塞状态了,就是线程的BLOCK状态,线程没有cpu执行权限,而且也不会释放占用的资源

4.重复造的轮子优势是啥

1.响应中断。可以接受中断信号,唤醒线程,就会释放锁,破坏了不可抢占条件。(自己获取的锁,别人抢不到)
2.支持超时。在一段时间内没有获取锁,不进入阻塞状态,而是返回错误,线程也会释放持有的锁,可以破坏不可抢占条件。
3.非阻塞获取锁。获取锁失败,不进入阻塞状态,而直接返回。这个线程有机会会释放曾经的锁

就可以弥补synchronized问题。

对应到Lock接口上的三个方法


// 支持中断的API
void lockInterruptibly() 
  throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit) 
  throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();

5.Lock如何保证可见性

synchronized中的可见性,是因为happens-before规则,
synchronized的解锁happens-before的加锁操作

那么Lock呢?


class X {
  private final Lock rtl =
  new ReentrantLock();
  int value;
  public void addOne() {
    // 获取锁
    rtl.lock();  
    try {
      value+=1;
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
}

利用了volatile相关的happens-before规则,
ReentrantLock里内嵌了一个volatile的成员变量state,获取锁的时候回读写state的值,解锁的时候也会读写state值。


class SampleLock {
  volatile int state;
  // 加锁
  lock() {
    // 省略代码无数
    state = 1;
  }
  // 解锁
  unlock() {
    // 省略代码无数
    state = 0;
  }
}

1.顺序性原则,线程T1,value += 1 Happens-before 释放锁的操作unlock()
2.volatile变量规则,由于state = 1会先读取state,所以线程T1的unlock()操作happens-before与线程T2的lock()操作
3.传递性原则,先T1的value += 1 Happens-before 与T2的lock()操作

6.可重入锁

线程可重复获取同一把锁


class X {
  private final Lock rtl =
  new ReentrantLock();
  int value;
  public int get() {
    // 获取锁
    rtl.lock();         ②
    try {
      return value;
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
  public void addOne() {
    // 获取锁
    rtl.lock();  
    try {
      value = 1 + get(); ①
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
}

线程T1执行到(1)处,已经获取到锁rtl,在(1)调用get方法,会在(2)再次对rtl进行加锁操作,可以再次加锁成功,非重入锁,那么线程T1就会被阻塞。

7.公平锁与非公平锁

ReentrantLock,两个构造函数,一个无参,一个传fair参数的构造函数
fair参数代表的是锁的公平策略
true就是公平锁,false就是非公平锁

公平与不公平,看的是,锁释放的时候的策略
公平锁,锁释锁,从等待队列中唤醒一个等待的线程,谁等待的时间长,就唤醒谁
非公平锁,释放锁的时候,不提供这个公平保证,可能等待的时间短的反而先被唤醒.

8.用锁的最佳实践

1.永远只在更新对象的成员变量时加锁
2.永远只在访问可变的成员变量时加锁
3.永远不再调用其他对象的方法时加锁

posted @ 2021-07-01 16:16  SpecialSpeculator  阅读(50)  评论(0编辑  收藏  举报