锁机制
<h1>锁</h1>
<p>多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制。</p>
<h2>3 名词解释</h2>
<ul>
<li>重入锁,(ReentrantLock)是一种递归无阻塞的同步机制。重入锁,也叫做递归锁,指的是同一线程 外层函
数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在JAVA环境下 ReentrantLock 和
synchronized 都是 可重入锁。</li>
<li>不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被
阻塞。 </li>
<li>互斥锁,用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,
如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解
锁。</li>
<li>自旋锁,与互斥量类似,它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。用
在以下情况:锁持有的时间短,而且线程并不希望在重新调度上花太多的成本。"原地打转"。 </li>
<li>偏向锁(Biased Locking)是Java6引入的一项多线程优化,它会偏向于第一个访问锁的线程,如果在运行过程中
,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程
加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上
的偏向锁,将锁恢复到标准的轻量级锁。 </li>
<li>轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,
偏向锁就会升级为轻量级锁。 </li>
<li>公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当
前线程线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到
自己 </li>
<li>
<p>非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。</p>
</li>
<li>读写锁,
<h2>2 方法锁、对象锁和类锁</h2></li>
<li>
<p>方法锁(synchronized修饰方法时)通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。
每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程
阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进
入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只
有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。</p>
</li>
<li>
<p>对象锁(synchronized修饰方法或代码块)当一个对象中有synchronized method或synchronized block的时候
调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。如果此对象的对象锁已被其他调用者占用,
则需要等待此锁被释放。(方法锁也是对象锁)。java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放
。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会
等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加
锁的1个好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。 </p>
</li>
<li>类锁(synchronized修饰静态的方法或代码块),由于一个class不论被实例化多少次,其中的静态方法和静态变
量在内存中都只有一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方
法,共用同一把锁,我们称之为类锁。对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态
变量互斥体)之间的同步。类锁只是一个概念上的东西,<strong>并不是真实存在的</strong>,它只是用来帮助我们理解锁定实例方
法和静态方法的区别的。java类可能会有很多个对象,但是只有1个Class对象,也就是说类的不同实例之间共享
该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有1个互斥
锁,而类的静态方法是需要Class对象。所以所谓的类锁,不过是Class对象的锁而已。获取类的Class对象有好几
种,最简单的就是[类名.class]的方式。
<h2>3 乐观锁与悲观锁</h2></li>
<li>悲观锁,
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直
到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操
作之前先上锁。</li>
<li>乐观锁,
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去
更新这个数据,可以使用版本号等机制来实现。
<code>update table set name='abc' version=version+1 where version = '本次更新的version'</code></li>
</ul>
<p>两种锁都各有优缺点,各自适用的场景不同。乐观锁适用于读多写少的场景,冲突很少发生,可以避免锁的开销,
提高吞吐量。但是读少写多的场景下,冲突经常发生,上层经常会进行retry,这样反而降低了性能,因此更适合
用悲观锁。</p>
<ul>
<li>CAS技术,
CAS也是一种乐观锁技术,多个线程去修改同一份数据的时候,只有一个线程能够修改成功。<code>CAS有3个操作数, 内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都 不做。</code>CAS要防止ABA问题(如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值
没有发生变化,但是实际上却变化了。),可以利用版本号来解决。
<h2>4 常见的锁</h2></li>
<li>Synchronized,它是一个非公平,悲观,独享,互斥,可重入的重量级锁。在原生语义上实现。</li>
<li>ReentrantLock,它是一个默认非公平但可实现公平的,悲观,独享,互斥,可重入,重量级锁。在API层面实
现,在JUC包下。</li>
<li>ReentrantReadWriteLock,它是一个默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重
量级锁。在API层面实现,在JUC包下。
<h3>4.1 ReentrantLock与synchronized 的区别</h3></li>
<li>ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。</li>
<li>在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,
Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;</li>
<li>ReentrantLock可以实现公平锁,Synchronized不行。</li>
<li>synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时
出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必
须将unLock()放到finally{}中。</li>
</ul>