synchronized
为什么用?怎么用?底层是怎么实现的?能否写一段代码展示死锁?顺便讲一下DCL单例模式咯?jdk做过哪些优化?
- 为什么用?
- 多线程环境为了保障共享资源访问的正确性,引入锁来确保一个时间点只有一个线程能执行被锁住的代码
- 怎么用?
- 修饰代码块
-
// 类锁
private void incr() { synchronized (this) { count++; } }
// 类锁private void incr() { synchronized (Count.class) { count++; } }
-
- 修饰方法
-
// 对象锁
private synchronized void incr() { count++; }
// 类锁 -
private static synchronized void incr() {count++; }
-
- 修饰代码块
- 底层实现
- 死锁代码
-
static Object a = new Object();
static Object b = new Object();public static void main(String[] args) { new Thread(() -> { synchronized (a) { System.out.println("t1 get lock a"); try { Thread.sleep(100); System.out.println("t1 wakes"); synchronized (b) { System.out.println("t1 get lock b"); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() -> { synchronized (b) { System.out.println("t2 get lock b"); try { Thread.sleep(100); System.out.println("t2 wakes"); synchronized (a) { System.out.println("t2 get lock a"); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
// 线程1抱着锁a睡着了,睡醒后要拿锁b 。 线程2抱着锁b睡着,睡醒后要拿锁a - 陷入相互等待
-
- DCL单例模式 - 空指针
-
public class SingletonPattern { private volatile static SingletonPattern singletonPattern = null; public static SingletonPattern getDCLSingleton() { if (singletonPattern == null) { synchronized (SingletonPattern.class) { if (singletonPattern == null) { singletonPattern = new SingletonPattern(); } } } return singletonPattern; } public static void main(String[] args) { System.out.println(getDCLSingleton()); } }
// 懒汉式单例 singletonPattern = new SingletonPattern(); 分解为三步 1.分配内存空间 2.初始化对象 3.将实例指向分配的地址 重排可能会执行 1-3-2 会出现空指针异常
// volatile 使用内存屏障禁止指令重排
-
- 锁的优化(jdk1.6) 为了避免使用重量级锁 - 锁是否可以降级由实现的jvm决定 https://www.cnblogs.com/xdyixia/p/9364247.html
- 偏向锁 - 减少轻量级锁自旋
- 持有偏向锁的线程不会自动释放,如果有线程竞争,才会释放锁
- 若竞争失败,偏向锁升级为轻量级锁。原来持有偏向锁的线程会释放锁(会导致stw操作)。
- 轻量级锁 - 减少重量级锁互斥
- 获取锁
- 拷贝Mark Word信息到自己线程的栈帧
- cas操作将锁对象Mark Word指向当前线程栈帧
- CAS若失败会判断锁对象Mark Word是否指向当前线程栈帧,若不是,则膨胀锁.Mark Word里面的指针就指向重量级锁
- 和释放锁都是通过CAS实现
- 持有轻量级锁的线程释放锁时会取出自己栈帧中之前复制的Mark Word数据 CAS替换锁对象的Mark Word。如果成功就释放成功
- 失败的话,说明Mark Word已经被改成重量级锁指针。已经有线程在阻塞了。在释放时要唤醒阻塞线程
- 获取锁
- 自旋锁 - 还是为了避免线程直接使用重量级锁
- 执行空循环等待 - 当然如果等待锁的时间过长,空循环也占用cpu
- 适应性自旋 -
- 对于同一个锁对象,如果上一次自旋获取到锁,且该锁被线程持有,那么其他获取锁的线程允许自旋更多的次数以获得该锁。
- 相反如果某个锁很少被自旋获取到,那么以后获取该锁时,自旋的次数将会变少,甚至可能省略自旋过程。
- 重量级锁
- 实现重量级锁,需要依赖操作系统切换用户态到内核态。切换成本高,消耗性能严重
- 实现 队列 - https://blog.csdn.net/kirito_j/article/details/79201213
- 锁升级过程
-
- 偏向锁 - 减少轻量级锁自旋
参考文章
- synchronized的实现原理及锁优化
- JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比
- 锁升级 https://www.cnblogs.com/kancy/p/10482756.html
是谁来自江河湖海,却囿于昼夜厨房与爱
浙公网安备 33010602011771号