1.锁的作用
首先先来了解一下我们为什么要使用到锁,我们都知道现在的计算机系统内部采用的都是多线程的处理方式,而且在我们自己设计的一些系统和数据库并发访问之中也会使用到多线程,那我们如何保证当多个线程访问一些数据时这些数据是安全的,就比如当前余额为100,线程A读取的时候,线程B把他修改了,导致线程A读到的数据不对,这时我们就需要用到锁这种机制。
2.锁分类
从性质上来划分区分为:乐观锁和悲观锁。
乐观锁:其实就是非常乐观的心态,默认认为在我当前线程执行过程中不会有其他的线程来修改我读取的资源,所以在这个过程中我们无需加锁也不用等待,只需要在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。
悲观锁:就是说,在任何时候我都认为会有其他线程来修改我当前线程所占有的资源,就会在当前线程占有的资源上加一个锁,表示这个资源只有获得锁的线程才能访问,其他线程等待,等上一个线程执行完释放锁之后,其他线程抢占锁,抢占到了就访问,这样是很安全,但是从另一个方面想,如果当前占有锁的线程执行时间特别长,其他资源一直等待,就会阻塞了大量的其他线程的工作,其次,在执行占有锁,释放锁的时候会涉及到上下文的切换,会消耗特别多的资源,如果线程比较多,那么就会损耗大量的系统资源。所以我们一般在系统中要尽可能的避免悲观锁的使用。
在Mysql中划分为:行级锁,表级锁,在MyISAM中默认使用的是表锁,在InnoDB中默认使用的是行锁,无论是行锁还是表锁都是悲观锁,注意行锁是加在索引上的(InnoDB中如果我们不设置索引,它默认会有一个隐式主键索引,不熟悉的可以看一下MVCC机制)。
行锁,表锁每一个又分为:读锁和写锁。
读锁:也叫共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响。也就是说当执行读操作的时候,多个线程的读操作可以同时执行,之间互不影响。
写锁:也叫排他锁,针对同一份数据,多个写操作无法同时执行,而且在进行写操作的时候,读操作也无法执行,就像名字一样具有排他性。
行锁表锁区别:
行锁:开销大,加锁慢,粒度小,行锁的并发冲突比较低,适合在大量并发请求的时候使用,在高并发的SQL读写操作下性能比较高,但是会出现死锁的情况
表锁:开销小,加锁快,粒度大,表锁的并发冲突比较高,在高并发的读写操作下效率比较低,但是不会出现死锁的情况。
注意:行锁必须有索引才能实现,没有索引的话就使用表锁
间隙锁:
我们都知道在Mysql的InnoDB存储引擎,它的隔离级别是RR(不熟悉的可以看一下Mysql事务隔离级别),但是却不会出现幻读的情况,这是因为它使用了间隙锁。
间隙锁:就像它的名字那样,锁住一个间隙,这个间隙只得是在一个事务中可能出现的新的数据,举例来说,假如emp表中只有101条记录,其empid的值分别是 1,2,...,100,101,下面的SQL:
Select * from emp where empid > 100 for update;
是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。这样新的数据就不会出现在当前事务中,也就不存在幻读的情况。
自旋锁:
在了解自旋锁之前我们先来了解一下CAS机制,我们都知道阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。而这就是我们为什么要使用CAS机制的原因
CAS是英文单词Compare and Swap的缩写,翻译过来就是比较并替换。
CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B,否则一直会循环请求,判断A和V是否相等,这样就避免了线程的阻塞和唤醒操作,减少了资源的浪费,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。
CAS的缺点:
1.CPU开销特别大,在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
2.会造成ABA问题。
偏向锁:
它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
轻量级锁:
轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;
重量级锁Synchronized:
在JDK1.5之前都是使用synchronized关键字保证同步的,Synchronized的作用相信大家都已经非常熟悉了,它可以把任意一个非NULL的对象当作锁。作用于方法时,锁住的是对象的实例(this)当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带,永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的程,synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码。
3.无锁机制(MVCC):
mvcc即多版本并发控制器,它是用来在不使用锁的情况下用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。