一. Lock,ReentrantLock介绍
了解lock之前可以对比jdk提供的synchronzied,synchronzied也被用于实现线程同步,但是有些场景下并不灵活,如多个同步方法,每次只能有一个线程访问;而Lock则可以非常灵活的在代码中实现同步机制。
Lock 接口的定义
public interface Lock {
// 获取锁,若当前lock被其他线程获取;则此线程阻塞等待lock被释放
// 如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁
void lock();
// 获取锁,若当前锁不可用(被其他线程获取);
// 则阻塞线程,等待获取锁,则这个线程能够响应中断,即中断线程的等待状态
void lockInterruptibly() throws InterruptedException;
// 来尝试获取锁,如果获取成功,则返回true;
// 如果获取失败(即锁已被其他线程获取),则返回false
// 也就是说,这个方法无论如何都会立即返回
boolean tryLock();
// 在拿不到锁时会等待一定的时间
// 等待过程中,可以被中断
// 超过时间,依然获取不到,则返回false;否则返回true
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 返回一个绑定该lock的Condtion对象
// 在Condition#await()之前,锁会被该线程持有
// Condition#await() 会自动释放锁,在wait返回之后,会自动获取锁
Condition newCondition();
}
ReentrantLock
ReentrantLock是唯一一个实现了Lock的接口的类,叫做可重入锁,意思是拥有锁之后,可以再次获取锁
二.基本使用
1.模拟上下班场景,公司要求A岗位同时只能安排一个人上班;
package com.lhy.jui.tools.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: lihuiyong
* @DATE : 2019/10/29
* @version:1.0.0
* @description: lock 使用场景
*
* 模拟保安值班,每次只能一个保安值班
*/
public class LockExample {
/**
* 默认创建非公平锁
*/
private Lock lock = new ReentrantLock();
private void workIng(){
System.out.println(Thread.currentThread().getName() +" 上班!");
}
private void workOff(){
System.out.println(Thread.currentThread().getName() +" 下班!");
}
public void work(){
try{
lock.lock();
workIng();
System.out.println(Thread.currentThread().getName() +"上班中");
Thread.sleep(100);
workOff();
}catch (Exception ex){
System.out.println(Thread.currentThread().getName()+"无法上班,有人还未下班");
}finally {
if(lock != null){
lock.unlock();
}
}
}
public void workTryLock(){
boolean b = lock.tryLock();
System.out.println(b);
if(b){
try {
workIng();
System.out.println(Thread.currentThread().getName() +"上班中");
workOff();
}catch (Exception ex){
ex.printStackTrace();
}finally {
if(lock != null){
lock.unlock();
}
}
}else{
System.out.println(Thread.currentThread().getName()+"无法上班,有人还未下班");
}
}
public static void main(String[] args) throws InterruptedException {
final LockExample lockExample = new LockExample();
List threadList = new ArrayList<>(50);
int i = 0 ;
do {
Thread a = new Thread(new Runnable() {
@Override
public void run() {
lockExample.work();
}
},"a_"+i);
Thread b = new Thread(new Runnable() {
@Override
public void run() {
lockExample.work();
}
},"b_"+i);
threadList.add(a);
threadList.add(b);
}while(i++ < 50);
threadList.forEach(s->{
s.start();
});
Thread.sleep(5000);
System.out.println("main over!");
}
}
为了效果展示,work方法 Thread.sleep(100);
效果如下 :
总结下lock的使用:
a.先创建一个lock对象,Lock lock = new ReentrantLock();
b.work方法进入,需要线程同步,开始先获取锁,若锁被其他线程占用,则阻塞
c.执行完work方法之后,unlock释放锁,释放锁和lock是成对出现的,确保unlock要在锁使用完之后及时释放,否则造成死锁;所以一般放在finally里面释放锁.
d.对比上面LockExample中的workTryLock使用的lock.tryLock();tryLock会尝试获取锁,方法有返回值;如果获取到锁,返回true;tryLock可以传等待时间,即在特定的时间内获取锁;
而lock.lock跟synchronzied一样,直接占用锁,无需返回值,其他线程进来若锁被占用,则会阻塞;
2.Lock和Condtion配合使用
有些场景都线程执行要顺序要求,即需要保证并发时入队和出队的要求,类似实现线程A的执行需要等待线程B执行完成才执行,
类似CountDownLatch的功能,但是它更多用于阻塞队列,生产者消费者的模式,实现等待通知机制;
我们模拟下并发线程中的有界队列
package com.lhy.jui.tools.lock;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: lihuiyong
* @DATE : 2019/10/31
* @version:1.0.0
* @description: lock 和 condition结合实现线程安全中的有界队列
*/
public class LockWithCondition {
public static class BlockQuene {
/**
* 队列
*/
private List quene = new LinkedList();
/**
* 队列大小
*/
private int queneLimit;
/**
* lock
*/
private Lock lock = new ReentrantLock();
/**
* take操作
*/
private Condition takeCondition = lock.newCondition();
/**
* put操作
*/
private Condition putCondition = lock.newCondition();
public BlockQuene(int queneLimit) {
this.queneLimit = queneLimit;
}
/**
* 入队
*/
public void pushQuene(T t) {
lock.lock();
try {
if (quene.size() == queneLimit) {
//队列已满,阻塞put操作
putCondition.await();
}
quene.add(t);
takeCondition.signal();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (lock != null) {
lock.unlock();
}
}
}
/**
* 出队
*/
public T popQuene() {
lock.lock();
T t = null;
try {
if (quene.isEmpty()) {
//队列为空,阻塞take操作
takeCondition.await();
}
putCondition.signal();
t = (T)quene.remove(0);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (lock != null) {
lock.unlock();
}
}
return t;
}
}
public static class ThreadPop implements Runnable{
private BlockQuene bolckQuene;
public ThreadPop(BlockQuene bolckQuene){
this.bolckQuene = bolckQuene;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" will pop.....");
Integer i = bolckQuene.popQuene();
System.out.println(" i="+i.intValue()+" alread pop");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class ThreadPush implements Runnable{
private BlockQuene bolckQuene;
public ThreadPush(BlockQuene bolckQuene){
this.bolckQuene = bolckQuene;
}
@Override
public void run() {
int i = 5;
while (i> 0){
try {
Thread.sleep(5000);
System.out.println(" i="+i+" will push");
bolckQuene.pushQuene(i--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
BlockQuene b = new BlockQuene(10);
Thread threadPop = new Thread(new ThreadPop(b));
Thread threadPush = new Thread(new ThreadPush(b));
threadPush.start();
threadPop.start();
}
}
运行效果 :
Condition与Lock配套使用,通过 lock.newConditin()
进行实例化
BlockQuene 类主要两个方法,一个入列一个出列,takeCondition 和 putCondition分别用于出列和入列的阻塞;通过pushQuene和popQuene实现入出队列的操作;
当popQuene时,如果队列为空,则用takeCondition 阻塞线程,若队列不为空,则直接pop,同时通知putCondition可以push;
当pushQuene时,如果队列已满,则用putCondition阻塞线程,若队列未满,加入队列同时通知takeCondition 可以pop了;
掌握这个原理之后,我们再去了解下阻塞队列BlockingQueue就比较简单了,后续会介绍BlockingQueue。