按照是否要加锁分为:悲观锁和乐观锁
悲观锁和乐观锁是按照加锁机制进行分类的,是一种设计理念,并不是具体的某把锁:
-
悲观锁是要加锁的,如Synchronization ,ReentrantLock 都是悲观锁 , 他的设计理念:就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放。
乐观锁是不加锁的:他的设计理念就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,不会上锁!但是如果 想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过, 则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作)。 说到乐观锁,就必须提到一个概念:CAS,什么是CAS呢?Compare-and-Swap,即比较并替换,也有叫做Compare-and-Set的,比较并设置。
乐观锁的使用场景如:Mysql 或 Elasticsearch 通过version来控制并发修改 ,也可以通过时间戳,UUID等来实现乐观锁。 比如:AtomicInteger 原子类使用到CAS思想也是乐观锁
-
他们的区别在于:悲观锁是在业务一开始就加锁,业务处理完成之后,释放锁,拿Mysql来说单执行加了 for update 的查询语句时就加锁,业务处理完成,事务提交就释放锁。而乐观锁是不加锁,只是最后做数据同步的时候,判断该条数据时候被别的线程修改过,来决定要不要执行当前业操作。 所以一个是加锁,一个是不加锁的。乐观锁的性能是更高的。但是从安全性上来说悲观锁是更安全的。
按照是否要阻塞分为:阻塞锁或者自旋锁
-
阻塞锁:阻塞锁如其名,就是当拿不到这个数据的锁,当前线程就会阻塞,直到被唤醒,相当于暂停这个线程的工作,让这个线程不会占用CPU时间,但缺点是线程恢复速度要比自旋锁慢,如Synchronization
-
自旋锁(不阻塞):当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会自旋,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock),要注意:长时间自旋会空耗CPU(忙等)
-
适应性自旋锁(不阻塞):在自旋锁的基础上自旋,尝试一定的次数还是获取不到锁就放弃获取锁,这种模式叫适应性自旋。
-
总结:线程竞争激烈的时候用阻塞锁,不激烈的时候用自旋锁。
按照是否要排队加锁分为:公平和非公平锁
-
公平锁(排队加锁):多个线程都在竞锁时是否要按照先后顺序排队加锁,如果是那就是公平锁
-
非公平锁(不排队加锁):多个线程都在竞锁时不需要排队加锁,是为非公平锁
按照是否可重入分为:重入锁和非重入锁
-
可重入锁:允许同一个线程多次获取同一把锁,是为可重入锁:比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫做递归锁)。Java里只要以Reentrant开头命名的锁都是可重入锁,Synchronization 也是可重入的
-
非可重入锁:一个线程在多个流程中不可用获取到同一把锁,是为非重入锁
按照锁的互斥特性分为:共享锁 和 排他锁
-
共享锁:多个线程可以共享一把锁,如多个线程同时读,一般是可共享读锁 :如读锁
-
排他锁:多个线程不可同时获取到一把锁,比如:lock ,synchronized锁