一、常见锁简单说明

1、悲观锁
悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候先加锁,确保数据的安全性。
锁实现:关键字synchronized、Lock接口的实现
使用场景:操作比较多,先加锁可以保证写操作时数据正确
2、乐观锁
乐观锁认为自己在使用数据的时候不会被别的线程修改,所以不会添加锁,只是在更新的时候去判断之前有没有别的线程更改过这个数据
锁实现:CAS算法,例如AtomicInteger类的原子自增底层是通过CAS实现的
使用场景:读多,不加锁的特点能够使读的性能大幅度提升

3、读锁(共享锁)
读锁即共享锁(S锁):共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。
4、写锁(排他锁
写锁即排他锁(X锁):用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
5、行锁
行锁即对数据表中每一行数据加锁,数据库最细粒度的锁,开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
实现:InnoDB

6、表锁
表锁即对数据库中每个表加锁,数据库中最大级别的锁,开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
实现:MyISAM、BDB、InnoDB
7、页锁
页锁即对组加锁,对相邻数据加锁,数据库中介于表锁和行锁之间的锁,开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
实现:BDB

8、互斥锁(重量级锁或阻塞同步、悲观锁)

互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它,互斥锁可适用于一个共享资源每次只能被一个线程访问的情况
9、自旋锁(CAS)
自旋锁在申请资源但是申请不到的情况下并不会挂起,而是会选择持续申请。这种锁结果适用于每个线程占用较少时间的锁,并且线程阻塞状态切换的代价远高于等待的代价时使用。
10、分布式锁
在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。
实现:数据库实现分布式锁; 缓存(Redis等)实现分布式锁; Zookeeper实现分布式锁;
11、区间锁(分段锁)
ConcurrentHashMap jdk1.7使用了分段锁来保证线程安全,效率比起使用synchronized的HashTable要高的很多。每个集合都可以看作是一个存储东西的房子,HashTable与ConcurrentHashMap存储的都是HashEntry数组(每个数组里面是链表,暂且忽略,直到就好)
12、重入锁
重入锁当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的 ,可避免死锁
锁实现:关键字synchronized,ReentrantLock锁实现
13、非重入锁
非重入锁与可重入锁相反,不可递归调用,递归调用就发生死锁。
锁实现:NonReentrantLockk锁实现
14、公平锁
公平锁多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
锁实现:ReentrantLock(true)锁实现
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
15、非公平锁
非公平锁多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:1、你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死 2、会发生羊群效应

二、锁的相关问题

1.阻塞和非阻塞

阻塞指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

2.同步和异步

  1. 同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成 后,通过状态、通知和回调来通知调用者。

3.什么是可重入锁

  1. 可重入锁的概念是自己可以再次获取自己的内部锁。举个例子,比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(如果不可重入的锁的话,此刻会造成死锁)。说的更高深一点可重入锁是一种递归无阻塞的同步机制。

4.什么叫读写锁

  1. 读写锁拆成读锁和写锁来理解。读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候其他线程都已经释放了读锁,而且该线程获取写锁之后,其他线程不能再获取读锁。简单的说就是写锁是排他锁,读锁是共享锁。

5.获取锁涉及到的两个概念即公平和非公平

  1. 公平表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO顺序。
  2. 非公平就是一种获取锁的抢占机制,和公平相对就是先来不一定先得,这个方式可能造成某些线程饥饿(一直拿不到锁)

三、ReentrantLock,Sychronized


3.1 synchronized


Synchronized的作用:使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。(阻塞锁)。

Lock接口:JavaSE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。

锁 - 图1.jpg)

3.2 ReentrantLock(可重入锁)


ReenTrantLock实现了Lock接口,

ReenTrantLock和synchronized都是可重入锁,作用基本相同

ReenTrantLock:同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

Synchronized:执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED 状态。

  1. public class ReetrantLockTest implements Runnable{
  2. private ReentrantLock lock =new ReentrantLock();
  3. @Override
  4. public void run() {
  5. sayHello();
  6. }
  7. public void sayHello(){
  8. try {
  9. lock.lock();
  10. //查询当前线程对此锁定的保持数。(持有锁为1不只有为0)
  11. System.out.println("前线程对此锁定的保持数(加锁时):"+lock.getHoldCount());
  12. //检查次方法是否持有锁
  13. System.out.println("是否持有锁:"+lock.isHeldByCurrentThread());
  14. System.out.println(Thread.currentThread().getName() + " locking ...");
  15. System.out.println("Hello world!");
  16. System.out.println(Thread.currentThread().getName() + " unlocking ..."+"\n");
  17. } finally {
  18. lock.unlock();
  19. }
  20. }
  21. public static void main(String[] args) {
  22. //确保是同一个对象
  23. ReetrantLockTest reetrantLockTest = new ReetrantLockTest();
  24. for ( int i = 0; i < 5; i++) {
  25. Thread thread =new Thread(reetrantLockTest,"线程"+i);
  26. thread.start();
  27. }
  28. }
  29. }

ReenTrantLock独有的能力:

  1. ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
  2. ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
  3. ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

3.3 ReentrantReadWriteLock(读写锁)


ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。

ReentrantReadWriteLock特点