什么是同步?
1. 执行同步

2. 数据同步
多份数据保持一致
是一个割裂的设计
- synchronized的设计是基于C/C++程序写出来的Monitor(JVM的实现)
- 其他的ReentrantLock等依赖AQS
ReentrantLock的优势(synchronized的缺陷)
1. Non-Blocking算法(tryLock) + timeout支持
timeout获取锁
var lock = new ReentrantLock();
// ...
try {
lock.tryLock(100, TimeUnit.MICROSECONDS);
} catch(InterruptedException e) {
e.printStackTrace();
}
-
2. 灵活使用
-
3. ReentrantLock支持中断能力
不能中断
var t = new Thread(() -> {
synchronized (lock) {
for(int i = 0; i < 10_0000_0000; i++) {}
System.out.println("finished");
}
});
t.start();
// 还是会print finished
// 需要自己在thread中写代码检查interrupt,需要自己去拉数据
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();
<a name="eJZjF"></a>
#### AQS
- Java提供的同步器开发框架,AbstractQueuedSynchronizer
- 将用户真正意义上和底层隔离,实现同步算法时不需要使用JVM的底层API
- 基于AQS实现互斥锁
```java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class Mutex {
private final Sync sync = new Sync();
static class Sync extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
protected boolean tryRelease(int arg) {
return compareAndSetState(1, 0);
}
}
public void lock() {
sync.acquire(0);
}
public void unlock() {
sync.release(0);
}
}
思考:synchronized关键字的设计
用法
synchronized(obj) {
// 临界区程序
}
方案1:一种不完全的实现方案
enter(&lock) {
while(!cas(&lock, 0, 1)) {
// 休眠
}
}
leave(&lock) {
lock = 0;
}
enter(&obj.lock);
// 临界区
leave(&obj.lock);
思考:对休眠的三种理解
- 休眠少量CPU周期(自旋锁)
- CPU负载高,但是速度快(有锁马上拿到了)
- 休眠少量CPU周期(自旋锁)
- 定时休眠,类似于Thread.sleep
- CPU消耗少,休眠和唤醒慢
- 定时可能有浪费,比如休眠100ms,但是第20ms就可以拿到锁了
- 定时休眠,类似于Thread.sleep
- 信号休眠、信号唤醒(类似于wait/notify)
- 能等待其他线程唤起,比定时休眠时间浪费少
- 信号休眠、信号唤醒(类似于wait/notify)
-
方案2: 改进,初版方案
优势:先尝试自旋锁(提升竞争少时的性能)
睡眠/唤醒一个线程的能力实现如何实现?
睡眠、唤醒线程的方法 ```java // 方法1: public static void main(String[] args) throws InterruptedException { var obj = new Object(); var t1 = new Thread(() -> {
System.out.printf("before-wait...1");
try {
synchronized (obj) {
// 是Java Monitor的能力
obj.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
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 />
- 解决:释放资源时唤醒休眠线程
- Monitor内部实现等待集合,没有竞争到资源的线程自旋失败后,在等待集合中休眠<br />
<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 />
- 示例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();
}
});
}
}