java 中实现锁的方式一般分为两种。
sychronized 关键字,和ReentrantLock.
这两种很好理解,也容易实现。但是名词可真有不少。下面解释一下
乐观锁、悲观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。。
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
自旋锁、适应性自旋锁
自旋锁:
尽可能的减少线程阻塞,唤醒,自己在那儿不停地循环设置用cas的方式获取锁。
private AtomicBoolean available = new AtomicBoolean(false);
public void lock(){
// 循环检测尝试获取锁
while (!tryLock()){
// doSomething...
}
}
public boolean tryLock(){
// 尝试获取锁,成功返回true,失败返回false
return available.compareAndSet(false,true);
}
public void unLock(){
if(!available.compareAndSet(true,false)){
throw new RuntimeException("释放锁失败");
}
}
这种方式也可以设置超时时间,比如在10s内不停的while(true),超过10秒获取失败。
适应性自旋锁:
一种更加聪明的自旋锁。某个线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源
公平锁、非公平锁
公平锁:这个是公平锁的实现方式,也就是谁先来的,先叫号,就能先拿到锁
// 队列票据(当前排队号码)
private AtomicInteger queueNum = new AtomicInteger();
// 出队票据(当前需等待号码)
private AtomicInteger dueueNum = new AtomicInteger();
private ThreadLocal<Integer> ticketLocal = new ThreadLocal<>();
public void lock(){
int currentTicketNum = dueueNum.incrementAndGet();
// 获取锁的时候,将当前线程的排队号保存起来
ticketLocal.set(currentTicketNum);
while (currentTicketNum != queueNum.get()){
// doSomething...
}
}
// 释放锁:从排队缓冲池中取
public void unLock(){
Integer currentTicket = ticketLocal.get();
queueNum.compareAndSet(currentTicket,currentTicket + 1);
}
非公平锁:先来后来都一样,有可能后来的先拿到锁。所以称之为非公平锁
比如synchronized关键字直接加到方法上面
可重入锁、非可重入锁
不可重入锁当一个线程获取到了锁 。其他线程(包含它自身)不能再次获取
public class Lock{
private boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
可重入锁
不可重入锁当一个线程获取到了锁 。其他线程(不包含它自身,自身多了个获取次数的计数而已)不能再次获取。
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock() throws InterruptedException{
Thread callingThread = Thread.currentThread();
while(isLocked && lockedBy != callingThread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock(){
if(Thread.curentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}
独享锁、共享锁
独享锁也叫排他锁,是指该锁一次只能被一个线程锁持有。
比如方法上面直接加上synchronized.就是独享锁。排他锁
共享锁是指:是指该锁可被多个线程所持有。
针对这个,很典型的实现是ReentrantReadWriteLock;
分析下ReentrantReadWriteLock的使用(不是源码哈),
1.读锁加上时,其他锁能加上读锁,能读,不能写,对读来说就是共享锁,对写来说就是排他锁
2.写锁加上时,不允许读,也不允许写。排他。
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//读取数据接口 这个是可以多次获取的。没啥问题
public void read(){
//上读锁,其他线程只能读不能写
rwl.readLock().lock();
// dosomething
//释放读锁,最好放在finnaly里面
rwl.readLock().unlock();
}
public void write(Object data){
//上写锁,不允许其他线程读也不允许写
rwl.writeLock().lock();
// dosomething
//释放写锁
rwl.writeLock().unlock();
}