# java-synchronize **Repository Path**: mbigger/java-synchronize ## Basic Information - **Project Name**: java-synchronize - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-01-17 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 项目说明 慕课网课程学习笔记,感谢老师的精彩分享,代码为我根据课程手动编写,仅供学习,版权归原作者所有。 [Java高并发之魂:synchronized深度解析](https://www.imooc.com/learn/1086) >简介:高并发问题向来是Java程序员进阶的重点,也是面试的难点。想要打通高并发的奇经八脉,synchronized是你不得不趟过的坑,本课程从synchronized,从使用方法到底层原理源码,娓娓道来。还对常见面试题和更深层扩展方面的思考,做出了讲解。本课程由浅入深,适合各阶段工程师观看。 # Synchronized的两种用法(对象锁和类锁) * 对象锁 * 方法锁 * 同步代码块 * 类锁 * synchronized修饰静态方法 * 制定锁为class对象(*.class) # 多线程访问同步方法的7种具体情况 1. 两个线程同时访问一个对象的同步方法 2. 两个线程访问的是两个对象的同步方法 3. 两个线程访问的是synchronized修饰的静态方法 4. 同时访问同步方法与非同步方法 5. 访问同一对象的不同的普通同步方法 6. 同时访问静态synchronized和非静态synchronized方法 7. 方法抛出异常后,会释放锁 # Java中常见的其它锁 * CountDownLatch * CyclicBarrier * 信号量 # 三点核心思想 1. 一把锁同时只能被一个线程获取,没有拿到锁的线程必须等待(对应到第1、5种情况) 2. 每个实例对应有自己的一把锁 ,不同实例之间互不影响,例外:锁对象是*.class及synchronized修饰的是static方法时,所有对象共用一把锁(对应第2、3、4、6种情况) 3. 无论方法是正常执行完成或者方法抛出异常,都会释放锁(对应第7种情况) # Synchronized的性质 #### 可重入 * 指的是统一线程的外层函数获得锁后,内层函数可以直接再次获得该锁 * 好处:避免死锁,提升封装性 #### 粒度 线程而非调用(用三种情况说明和pthread的区别) * 证明同一方法是可重入的 * 证明可重入不要求是同一方法 * 证明可重入不要求是同一个类中 #### 不可中断 一旦这个锁被别人获得了,如果我还想获得,我只能选择等待后者阻塞,直到别的现场释放这个锁,托孤别人永远不是放这个锁,你玩么我只能永远等待下去。 # 深入原理 * 加锁和释放锁的原理,现象、时机、深入JVM看字节码 **总结:**每一个类的实例对应着一把锁,每个synchronized方法必须首先获得调用该方法的类的实例的锁,方法才能执行,否则线程会阻塞,而这个方法一旦执行了,他就独占了这把锁,直到方法返回或者抛出异常,才将锁释放,此后哪些被阻塞的线程才能获得这把锁,重新进入到可执行状态。 **现象:**这就意味着一个对象中有synchronized修饰的方法或者代码块的时候,想要执行这段代码就必须先获得这个对象锁,如果此对象的对象锁已经被其它调用者占用,就必须等待到它被释放,所有的Java对象都含有一个互斥锁,这个锁由JVM自动获取和释放,我们只要指定这个对象就可以了,至于锁的释放和获取不需要使用者操心,以上就是咱门看到的现象。 **时机:**每个Java对象都可以用作实现同步的锁,这个锁成为内置锁,或者叫做监视器锁,英文名称叫做Monitor Lock,线程在进入到同步代码块之前,就会自动获得这个锁,并且在退出代码块时会自动地释放。无论时通过正常的退出,还是异常退出,他都会释放,那么获得这个内置锁的唯一途径就是进入到这个内置锁所保护的同步代码块或者方法中,这样一来,我们就理解了它的时机。 **原理:**monitorenter和monitorexit指令会在执行的时候对对象的锁计数加一或者减一,这个操作和操作系统里的PV操作很像,实际上每一个对象都与一个monitor相关联,而一个monitor的lock锁只能被一个线程在同一时间获得,一个线程在尝试获得与这个对象关联的monitor所有权的时候,只会发生以下三种情况 1. 如果这个monitor计数器为0,这意味着目前还没有被获得,所以这个线程就会立刻获得,然后把这个计数器加1,一旦加1之后,别人在想进来,就会看到这个信号,就知道它已经被别人持有了,所以加1也就意味着当前线程是这个monitor的所有者,这是第一种情况,成功获得锁。 2. 第二种情况呢,是如果说如果monitor已经拿到了所有权,又重入了,这样会导致计数器再加1,就是说已经有了这把锁,再次重入的情况。 3. 第三种情况,如果monitor已经被其它线程所持有了,而我去获取她的时候,那只能得到:不好意思,你活去不了的信号,那我就只能进入阻塞状态,直到monitor计数器变为0,才会再次去尝试获取这个锁。 monitorexit的作用就是给monitor计数器减1,如果为减完之后变成0了,那就意味着当前线程不在拥有monitor的所有权,如果减完之后不为0,那就意味着是可重入进来的,那他还继续持有这把锁。如果最终减到0后,这就意味着其它被阻塞的线程会再次尝试获取对该把锁的所有权。 * 可重入原理: 加锁次数计数器 * JVM负责跟踪对象被加锁的次数 * 线程第一次给对象加锁的时候,计数变为1。每当这个相同的线程在此对象上再次获得锁时,计数会递增 * 每当任务离开时,计数递减,当计数为0的时候,锁被完全释放 * 保证可见性的原理: 内存模型 # Synchronized的缺陷 * 效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个试图获得锁的线程 * 不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够。 * 无法知道是否成功获取到锁 # 常见面试题 1. synchronized 使用注意点 * 锁对象不能为空 * 作用域不宜过大 * 避免死锁 2. 如何选择Lock和synchronized关键字? * 优先使用java提供的包,如CountDownLatch、CyclicBarrier、Semaphore信号量、Atomic原子类; * synchronized如果适用,优先使用 * 如果用到了lock独有的特性,那就使用它 3. 多线程访问同步方法的各种具体情况 # 思考题 1. 多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的是哪个线程? JVM线程调度机制 2. Synchronized使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能? 3. 我想更灵活地控制锁的获取和释放(现在释放锁的时机都被对顶死了),怎么办? 4. 什么是锁的升级、降级?什么是JVM里的偏斜锁、轻量级锁、重量级锁? # 课程总结 * **一句介绍synchronized:** JVM会自动通过使用monitor来加锁和解锁,保证同时只有一个线程可以执行指定的代码,从而保证了线程安全,同时具有可重入和不可中断的性质。