目录
一、前言
二、锁
三、总结
一、前言
原子性问题的源头就是线程切换,但在多核 CPU的大背景下,不允许线程切换是不可能的
互斥: 同一时刻只有一个线程执行
上面这句话的意思是: 对共享变量的修改是互斥的,也就是说线程 A 修改共享变量时其他线程不能修改,这就不存在操作被打断的问题了
二、锁
synchronized 的三种用法:
public class ThreeSync {
private static final Object object = new Object();
public synchronized void normalSyncMethod(){
//临界区
//对于普通同步方法,锁的是当前实例对象,通常指 this
}
public static synchronized void staticSyncMethod(){
//临界区
//对于静态同步方法,锁的是当前类的 Class 对象,如 ThreeSync.class
}
public void syncBlockMethod(){
synchronized (object){
//临界区
//对于同步方法块,锁的是 synchronized 括号内的对象
}
}
}
- 对于普通同步方法,锁的是当前实例对象,通常指 this
- 对于静态同步方法,锁的是当前类的 Class 对象,如 ThreeSync.class
- 对于同步方法块,锁的是 synchronized 括号内的对象
那什么是临界区呢??
临界区: 我们把需要互斥执行的代码看成为临界区
如何用锁保护有效的临界区才是关键
线程进入临界区之前,尝试加锁 lock(), 加锁成功,则进入临界区(对共享变量进行 修改),持有锁的线程执行完临界区代码后,执行 unlock(),释放锁。针对这个模 型,大家经常用抢占厕所坑位来形容
?
- 我们锁的是什么?
- 我们保护的又是什么?
?资源 R (共享变量) 就是我们要保护的资源,所以我们就要创建资源 R 的锁来保护资源 R
-
编写串行程序时,是不建议 try...catch 整个方法的,这样如果出现问是很难定位的,道理一样,我们要用锁精确的锁住我们要保护的资源就够了,其他无意义的资源是不要锁的 -
锁保护的东?越多,临界区就越大,一个线程从走入临界区到走出临界区的时间就越?,这就让其他线程等待的时间越久,这样并发的效率就有所下降,其实这是涉及到锁粒度的问题
public class ValidLock {
private static final Object object = new Object();
private int count;
public synchronized void badSync(){
//其他与共享变量count无关的业务逻辑
count++;
}
public void goodSync(){
//其他与共享变量count无关的业务逻辑
synchronized (object){
count++;
}
}
}
public class UnsafeCounter {
private static int count;
public synchronized void counter(){
count++;
}
public static synchronized int calc(){
return count++;
}
}
一个锁的是 this,一个锁的是 UnsafeCounter.class, 他们都想保护共享变量 count
两个临界区是用两个不同的锁来保护的,所以临界区没有互斥关系,也就不能保护 count,所以这样加锁是无意义的
三、总结
- 解决原子性问题,就是要互斥,就是要保证中间状态对外不可?
- 锁是解决原子性问题的关键,明确知道我们锁的是什么,要保护的资源是什么,更重要的要知道你的锁能否保护这个受保护的资源
-
有效的临界区是一个入口和一个出口,多个临界区保护一个资源,也就是一个资源有多个并行的入口和多个出口,这就没有起到互斥的保护作用,临界区形同虚设 -
锁自己家?能保护资源就没必要锁整个小区,如果锁了整个小区,这严重影响 其他业主的活动(锁粒度的问题)
?
|