什么是同步?

1. 执行同步
image.png

2. 数据同步

  • 多份数据保持一致

    • 缓存和存储的同步
    • 不同机房订单数据的同步


      Java同步器设计

      整体架构

  • 是一个割裂的设计

    • synchronized的设计是基于C/C++程序写出来的Monitor(JVM的实现)
    • 其他的ReentrantLock等依赖AQS

image.png

ReentrantLock的优势(synchronized的缺陷)

1. Non-Blocking算法(tryLock) + timeout支持

  • timeout获取锁

    1. var lock = new ReentrantLock();
    2. // ...
    3. try {
    4. lock.tryLock(100, TimeUnit.MICROSECONDS);
    5. } catch(InterruptedException e) {
    6. e.printStackTrace();
    7. }
  • Lock Free

    2. 灵活使用

  • ReentrantLock可以跨语句块

    3. ReentrantLock支持中断能力

  • 不能中断

    1. var t = new Thread(() -> {
    2. synchronized (lock) {
    3. for(int i = 0; i < 10_0000_0000; i++) {}
    4. System.out.println("finished");
    5. }
    6. });
    7. t.start();
    8. // 还是会print finished
    9. // 需要自己在thread中写代码检查interrupt,需要自己去拉数据
    10. t.interrupt();
  • ReentrantLock支持中断后走到catch逻辑 ```java var lock = new ReentrantLock();

var t = new Thread(() -> { int k = 0; try { lock.lockInterruptibly(); for(int i = 0; i < 10_0000_0000; i++) { k += 1; } System.out.println(“finished: “ + k); // 中断后马上走到异常 } catch(InterruptedException e) { System.out.println(“interrupt: “ + k); e.printStackTrace(); } finally { lock.unlock(); } }); t.start(); t.interrupt();

  1. <a name="eJZjF"></a>
  2. #### AQS
  3. - Java提供的同步器开发框架,AbstractQueuedSynchronizer
  4. - 将用户真正意义上和底层隔离,实现同步算法时不需要使用JVM的底层API
  5. - 基于AQS实现互斥锁
  6. ```java
  7. import java.util.concurrent.locks.AbstractQueuedSynchronizer;
  8. public class Mutex {
  9. private final Sync sync = new Sync();
  10. static class Sync extends AbstractQueuedSynchronizer {
  11. protected boolean tryAcquire(int arg) {
  12. return compareAndSetState(0, 1);
  13. }
  14. protected boolean tryRelease(int arg) {
  15. return compareAndSetState(1, 0);
  16. }
  17. }
  18. public void lock() {
  19. sync.acquire(0);
  20. }
  21. public void unlock() {
  22. sync.release(0);
  23. }
  24. }

思考:synchronized关键字的设计

用法

  1. synchronized(obj) {
  2. // 临界区程序
  3. }

方案1:一种不完全的实现方案

  1. enter(&lock) {
  2. while(!cas(&lock, 0, 1)) {
  3. // 休眠
  4. }
  5. }
  6. leave(&lock) {
  7. lock = 0;
  8. }
  9. enter(&obj.lock);
  10. // 临界区
  11. leave(&obj.lock);

思考:对休眠的三种理解

    1. 休眠少量CPU周期(自旋锁)
      • CPU负载高,但是速度快(有锁马上拿到了)
    1. 定时休眠,类似于Thread.sleep
      • CPU消耗少,休眠和唤醒慢
      • 定时可能有浪费,比如休眠100ms,但是第20ms就可以拿到锁了
    1. 信号休眠、信号唤醒(类似于wait/notify)
      • 能等待其他线程唤起,比定时休眠时间浪费少
  • 所以改进方案是1和3的的结合,先少量自旋,然后进入信号休眠

    方案2: 改进,初版方案

    image.png

    优势:先尝试自旋锁(提升竞争少时的性能)

    睡眠/唤醒一个线程的能力实现如何实现?

  • 睡眠、唤醒线程的方法 ```java // 方法1: public static void main(String[] args) throws InterruptedException { var obj = new Object(); var t1 = new Thread(() -> {

    1. System.out.printf("before-wait...1");
    2. try {
    3. synchronized (obj) {
    4. // 是Java Monitor的能力
    5. obj.wait();
    6. }
    7. } catch (InterruptedException e) {
    8. e.printStackTrace();
    9. }
    10. System.out.printf("after-wait...1");

    });

    var t2 = new Thread(() -> {

    System.out.printf("before-wait...2");
    
    try {
      synchronized (obj) {
        obj.wait();
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.printf("after-wait...2");
    

    });

    t1.start(); t2.start();

    Thread.sleep(100); synchronized (obj) {

    obj.notifyAll();
    

    } }

// 方法2: 基于ReentrantLock public static void main(String[] args) throws InterruptedException { var lock = new ReentrantLock(); Condition waitCond = lock.newCondition();

var t1 =  new Thread(() -> {
    System.out.printf("before-wait...1");

    try {
        lock.lock();
        waitCond.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }

    System.out.printf("after-wait...1");
});

var t2 =  new Thread(() -> {
    System.out.printf("before-wait...2");

    try {
        lock.lock();
        waitCond.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
    System.out.printf("after-wait...2");
});

t1.start();
t2.start();

lock.lock();
waitCond.signalAll();
lock.unlock();

}


- 如何自己实现?
   - 操作系统API修改线程状态:READY -> SLEEPING
   - JVM知道哪些线程在休眠:WaitSet
<a name="uSxfx"></a>
#### 再看synchronized关键字需要的能力

- 1. 需要实现锁/解锁的逻辑
- 2. 需要自旋锁到休眠的升级逻辑
- 3. API设计:每个对象都可以上锁,`synchronized(任何东西){...}`
- 4. 线程在竞争不到资源时休眠
- 5. 释放资源时唤醒休眠线程,需要知道有哪些休眠线程

<a name="KKr9Y"></a>
## Monitor:synchronized实现方案(方案3)
很多语言都有Monitor相关实现
<a name="CGqJS"></a>
### Monitor

- 每个对象都关联一个Monitor
- 解决:每个对象都可以上锁问题(不用再定义锁变量了)
- 线程抢占锁,就是抢占Monitor。线程把自己往Monitor的owner上写<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/128789/1627656016278-4e9cb1dd-c449-4eb4-8d53-f4609b11d55f.png#clientId=uc338a3e4-593d-4&from=paste&height=195&id=ud5ca4543&margin=%5Bobject%20Object%5D&name=image.png&originHeight=338&originWidth=569&originalType=binary&ratio=1&size=40896&status=done&style=none&taskId=u336d3e4f-4d72-4b60-927b-8bfb458f96e&width=329)
- 解决:释放资源时唤醒休眠线程
- Monitor内部实现等待集合,没有竞争到资源的线程自旋失败后,在等待集合中休眠<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/128789/1627656099939-7108621e-8f4b-40a2-95e1-febc3025d843.png#clientId=uc338a3e4-593d-4&from=paste&height=122&id=u7f41a104&margin=%5Bobject%20Object%5D&name=image.png&originHeight=164&originWidth=719&originalType=binary&ratio=1&size=23460&status=done&style=none&taskId=u8f392842-4fb0-4482-a4e0-98431e4bd45&width=533)
<a name="ZkMQi"></a>
### 能力汇总:JVM的Monitor指令
<a name="ltx1W"></a>
#### monitorenter

- 获得锁 -> 进入临界区
- 失败:自旋 -> 休眠 -> WaitSet
<a name="F9dTj"></a>
#### 临界区
<a name="drmPn"></a>
#### monitorleave

- 唤醒

<a name="y7A3k"></a>
## Monitor的更多设计因素
<a name="NLJuY"></a>
### 线程间协作
<a name="fEQDs"></a>
#### Java如何实现生产者/消费者?
<a name="cqBM2"></a>
#### 读/计算/写的模型

- Polling
   - while循环不断检查有没有新的读入
   - 这种设计没有Queuing设计合理
- Queuing
   - 实现生产者消费者<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/128789/1627657139133-f687d02b-7d53-40cd-9e63-1c52e749040d.png#clientId=uc338a3e4-593d-4&from=paste&height=191&id=u7b646abe&margin=%5Bobject%20Object%5D&name=image.png&originHeight=308&originWidth=776&originalType=binary&ratio=1&size=101039&status=done&style=none&taskId=ue87f53b3-8746-4442-942f-6c63fbe934b&width=480)
   - 示例Coding

```java
import java.util.LinkedList;

public class ProducerCustomerModel {
  final static int MAX = 10;
  // 生产者队列
  LinkedList<Integer> queue = new LinkedList<>();

  // 模拟生产过程,Thread.sleep就是生产成本
  int readData() throws InterruptedException {
    Thread.sleep((long) Math.random() * 1000);
    return (int) Math.floor(Math.random());
  }

  // Producer
  public void readDb() throws InterruptedException {
    synchronized (queue) {
      // 满了就不再生产
      if (queue.size() == MAX) {
        return;
      }
      var data = readData();
      queue.add(data);
    }
  }

  // Comsumer
  public void calculate() {
    if (queue.size() == 0) {
      return;
    }
    var data = queue.remove();
    System.out.println("queue-size: " + queue.size());
    data *= 100;
  }

  public static void main(String[] args) {
    var p = new ProducerCustomerModel();
    // 开100个线程去生产
    for (int i = 0; i < 100; i++) {
      new Thread(() -> {
        while (true) {
          try {
            p.readDb();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }).start();
    }

    new Thread(() -> {
      while (true) {
        p.calculate();
      }
    });
  }
}