锁是用于控制多线程访问共享资源的工具。通常,锁提供对共享资源的独占访问:一次只有一个线程可以获取锁,对共享资源的所有访问都需要首先获取锁。但是,一些锁可以允许同时访问共享资源,例如读写锁ReadWriteLock。
锁的分类
a.悲观锁与乐观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
b.公平锁与非公平锁
公平锁:线程获取锁的时机是按照当前线程请求的绝对时间顺序(当新线程尝试获取锁的时候,除非同步队列为空,否则新线程必须进入同步队列按序获取锁)。
非公平锁:线程获取锁的时机是按照当前锁的状态来获取(当新线程尝试获取锁的时候,如果当前锁被持有,则加入队列,相当于公平锁;如果当前锁刚好被释放,则直接获得锁,而不用进入同步队列。相当于插队)。
c.可重入锁:当线程持有锁后,如果它再次尝试去获取该锁时它会立刻获取到该锁,而不会需要进入同步队列等待。当然每次获取锁的操作都需要对应的释放锁操作。
按照不同的性质,大体有以下分类的锁(彼此之间不是排斥关系,意思就是有些锁既可以是悲观锁也可以是公平锁,还可以是可重入的)。
Lock接口
在Lock接口出现之前,Java程序是靠synchronized关键字来实现锁功能的,而Java SE 5之后,并发包(java.util.concurrent,JUC)中新增了Lock接口以及相关实现类,来实现锁的功能。相比较之下,锁的最大优势就是可以自主控制,Lock 接口实现类提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,Lock接口的实现类允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁。Lock接口提供了以下接口让具体的实现类来完成:
- void lock():获取锁,如果锁不可用,则当前线程将出于线程调度目的而禁用,并处于休眠状态,直到获得锁为止。
- void lockInterruptibly():可中断的获取锁,和lock方法的不同之处在于该方法会响应中断
- boolean tryLock():尝试非阻塞的获取锁,调用该方法后会立刻返回,如果获取成功返回true,否则返回false
- boolean tryLock():超时的获取锁,直到当前线程获得锁 | 被中断 | 超时结束 返回
- void unlock( ):释放锁
- Condition newCondition():创建一个等待队列,并与当前的锁绑定。当前线程只有获取了锁,才能调用等待队列的wait()方法,然后将此线程转移至等待队列,并释放锁。
ReadWriteLock接口
一个ReadWriteLock维护一个用于读操作read Lock和一个用于写操作的write Lock。没有写线程的情况下,多个读线程可以同时持有读lock,而写lock是独占的。