19.Lock锁介绍与应用案例
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了19.Lock锁介绍与应用案例相关的知识,希望对你有一定的参考价值。
在JUC中,Lock是一个接口,其作用与synchronized类似,都是为了保证线程安全而提前加锁,不同的是Lock只定义了与抢锁和释放锁相关的操作,没有具体实现,而且要用lock()和unlock()将要保护的代码给包起来才可以。其中常用的接口有三个:
-
lock():抢占锁资源方法,如果当前线程没有抢占到锁,则阻塞。
-
trylock():尝试抢占锁资源,如果抢占成功则返回true,否则返回false。
-
unlock():释放锁。
Lock既然是一个接口,那么必须有具体的实现才可以,常见的有三个:
-
重入锁ReentrantLock
-
读写锁ReentrantReadWriteLock
-
读写锁的改进版StampedLock
1 重入锁ReentrantLock
ReentrantLock是一个支持重入的排他锁。所谓排他就是同一时刻只允许一个线程获得锁。
那重入是什么意思呢?意思是如果一个线程获得了锁,那么后面再访问相同的资源时,不需要再加锁就可以访问,一般此时会记录重入次数的。如果还是不清楚,我们看一个例子:
void update(a)
insert(a);
这是我们常见的一种写法,在update某个记录的时候,我们可能在其内部要先做一个插入操作,一般来说,update和insert都需要先对资源进行加锁。如果操作的是同一个资源,比如这里的update对a加锁了,那执行insert时一定还没有释放,那insert该如何获得锁呢?
此时最好的方式就是让a在执行insert时可以直接获得锁资源,完成之后updat再继续执行,这就是重入的意思,synchronized和ReetrantLock都支持重入。
重入锁的例子:
public class ReentrantLockExample
static Lock lock=new ReentrantLock();
private int count = 0;
public void incr()
lock.lock();
try
count++;
finally
lock.unlock();
public static void main(String[] args) throws InterruptedException
ReentrantLockExample atomicExample = new ReentrantLockExample();
Thread[] threads=new Thread[2];
for (int j = 0;j<2;j++)
threads[j]=new Thread(() ->
for (int k=0;k<10000;k++)
atomicExample.incr();
);
threads[j].start();
threads[0].join();//保证线程执行结束
threads[1].join();
System.out.println(atomicExample.count);
上面的例子中,针对count++进行了加锁,从而保证count++在多线程访问下的线程安全性。
2 读写锁ReentrantReadWriteLock
ReentrantReadWriteLock表示可以重入的读写锁,那什么是读写锁呢?想象一下,开会的时候领导在画大饼,大家可以同时看,如果两个领导同时画,我们该怎么看呢?这就是读写的实际价值。读的时候可以多个人同时读,而写的时候只能一个人写。另外为了防止写的过程中,读到的信息可能前后不一致,因此一般写的时候也不能读,具体来说读写锁的特征就是:
-
读/读不互斥,多个线程访问,线程不会阻塞。
-
读/写互斥,如果一个读一个写,此时就要阻塞一个。
-
ReentrantReadWriteLock写/写互斥,多个线程同时写,要互斥
看个例子:
public class ReadWriteLockExample1
private final Lock lock=new ReentrantLock();
private List<String> dataList=new ArrayList<>();
public void add(String data)
lock.lock();
try
dataList.add(data);
finally
lock.unlock();
public String get(int idx)
lock.lock();
try
return dataList.get(idx);
finally
lock.unlock();
这个例子中,dataList不是线程安全的,为此采用重入加锁机制,但是这里的get()和add()是同一级别的操作。如果get()操作很多,那么add()就会被阻塞。不过get方法本身不会影响到数据,因此对get的加锁是不必要的,因此我们需要一个更合理的加锁方法,这就是读写锁。
基于读写锁的代码如下:
public class ReadWriteLockExample
private final ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private final Lock readLock=readWriteLock.readLock();
private final Lock writeLock=readWriteLock.writeLock();
private List<String> dataList=new ArrayList<>();
public void add(String data)
writeLock.lock();
try
dataList.add(data);
finally
try
Thread.sleep(5000);
catch (InterruptedException e)
e.printStackTrace();
writeLock.unlock();
public String get(int idx)
readLock.lock();
try
return dataList.get(idx);
finally
readLock.unlock();
上面代码中,通过读锁readLock和writeLock把读写的操作做了分离,从而减少读锁带来的竞争,其具体实现原理,我们后面章节再看。
3 StampedLock
ReentrantReadWriteLock有个问题,就是如果有get()操作,那么所有的add()必须等待,也就是读的过程中是不允许写的,而如果get()过多,就会导致写线程一直被阻塞。为了解决这个问题,Jdk8引入了StampedLock锁,对读写访问进行了优化,其基本原理是提供了一种乐观的策略,当有线程调用get()方法读取数据时,不会阻塞准备执行写操作的过程,具体使用方法如下:
public class StampedLockExample
private int balance;
private StampedLock lock = new StampedLock();
public StampedLockExample()
balance=10;
public void conditionReadWrite (int value)
// 首先判断balance的值是否符合更新的条件
long stamp = lock.readLock();
while (balance > 0)
long writeStamp = lock.tryConvertToWriteLock(stamp);
if(writeStamp != 0) // 成功转换成为写锁
stamp = writeStamp;
balance += value;
break;
else
// 没有转换成写锁,这里需要首先释放读锁,然后再拿到写锁
lock.unlockRead(stamp);
// 获取写锁
stamp = lock.writeLock();
lock.unlock(stamp);
public void optimisticRead()
long stamp = lock.tryOptimisticRead();
int c = balance;
// 这里可能会出现了写操作,因此要进行判断
if(!lock.validate(stamp))
// 要重新读取
stamp = lock.readLock();
try
c = balance;
finally
lock.unlockRead(stamp);
System.out.println("读取的值为:"+c);
public void read ()
long stamp = lock.readLock();
lock.tryOptimisticRead();
int c = balance;
System.out.println("读取的值为:"+c);
// ...
lock.unlockRead(stamp);
public void write(int value)
long stamp = lock.writeLock();
balance += value;
lock.unlockWrite(stamp);
public static void main(String[] args)
StampedLockExample demo=new StampedLockExample();
new Thread(new Runnable()
@Override
public void run()
while(true)
demo.read();
demo.optimisticRead();
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
).start();
new Thread(new Runnable()
@Override
public void run()
while(true)
demo.write(2);
demo.conditionReadWrite(3);
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
).start();
以上是关于19.Lock锁介绍与应用案例的主要内容,如果未能解决你的问题,请参考以下文章
Mysql 死锁过程及案例详解之记录锁与间隔锁Record Lock Gap Lock