突击并发编程 JUC 系列演示代码地址:
https://github.com/mtcarpenter/JavaTutorial
锁是用来管制多个线程访问共享资源的形式,通过锁能够避免多个线程同时访问共享资源。在 Java1.5
之前实现锁只能应用 synchronized
关键字实现,然而 synchronized
隐式获取开释锁,在 1.5
之后官网新增了 lock
接口也是用来实现锁的性能,,它具备与 synchronized
关键字相似的同步性能,显式的获取和开释锁。lock
领有了锁获取与开释的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized
关键字所不具备的同步个性。
LOCK 办法阐明
void lock()
: 获取锁,调用该办法以后线程将会获取锁,当锁取得后,从该办法返回void lockInterruptibly() throws InterruptedException
: 可中断地获取锁,和lock
办法地不同之处在于该办法会响应中断,即在锁的获取中能够中断以后线程boolean tryLock()
: 尝试非阻塞地获取锁,调用该办法后立即返回,如果可能获取则返回 true 否则 返回 false-
boolean tryLock(long time, TimeUnit unit)
: 超时地获取锁,以后线程在以下 3 种状况下会返回:- 以后线程在超时工夫内取得了锁
- 以后线程在超时工夫被中断
- 超时工夫完结后,返回 false
void unlock()
: 开释锁Condition newCondition()
: 获取锁期待告诉组件,该组件和以后的锁绑定,以后线程只有取得了锁,能力调用该组件的wait()
办法,而调用后,以后线程将开释锁。
ReentrantLock 简介
Lock
作为接口类为咱们提供一组办法,只能通过的实现类进行 Lock
办法,明天咱们就讲讲继承 Lock
接口一个可重入的独占锁 ReentrantLock
实现类,ReentrantLock
通过自定义队列同步器(Abstract Queued Sychronized,AQS
)来实现锁的获取与开释。它应用了一个 int 成员变量示意同步状态,通过内置的 FIFO
队列来实现资源获取线程的排队工作,并发包的作者(Doug Lea)冀望它可能成为实现大部分同步需要的根底。
独占锁指该锁在同一时刻只能被一个线程获取,而获取锁的其余线程只能在同步队列中期待;可重入锁指该锁可能反对一个线程对同一个资源执行屡次加锁操作。ReentrantLock
反对偏心锁和非偏心锁的实现。偏心指线程竞争锁的机制是偏心的,而非偏心指不同的线程获取锁的机制是不偏心的。ReentrantLock
岂但提供了 synchronized
对锁的操作性能,还提供了诸如可响应中断锁、可轮询锁申请、定时锁等防止多线程死锁的办法。
ReentrantLock 办法阐明
ReentrantLock()
: 无参ReentrantLock
应用的非偏心锁。ReentrantLock(boolean fair)
:ReentrantLock
能够初始化设置是偏心锁锁,还是非偏心锁。getHoldCount()
:查问以后线程在某个Lock
上的数量,如果以后线程胜利获取了Lock
,那么该值大于等于 1;如果没有获取到Lock
的线程调用该办法,则返回值为 0。isHeldByCurrentThread()
:判断以后线程是否持有某个Lock
,因为Lock
的排他性,因而在某个时刻只有一个线程调用该办法返回 true。isLocked()
:判断Lock
是否曾经被线程持有。isFair()
:创立的ReentrantLock
是否为偏心锁。hasQueuedThreads()
:在多个线程试图获取Lock
的时候,只有一个线程可能失常取得,其余线程可能(如果应用tryLock()
办法失败则不会进入阻塞)会进入阻塞,该办法的作用就是查问是否有线程正在期待获取锁。hasQueuedThread(Thread thread)
:在期待获取锁的线程中是否蕴含某个指定的线程。getQueueLength()
:返回以后有多少个线程正在期待获取锁。
伪代码回顾
精彩片段 1:
class X {private final ReentrantLock lock = new ReentrantLock();
public void m() {
// 加锁
lock.lock();
try {// 业务执行} finally {
// 开释锁
lock.unlock()}
}
}
精彩片段 2:
class X {private final ReentrantLock lock = new ReentrantLock();
public void m() {
// 尝试获取锁
if (lock.tryLock()) {
try {// 解决工作 .......} catch (Exception ex) { } finally {
// 开释锁
lock.unlock();}
} else {
//else 示意没有获取锁 无需敞开
// ..... 依据理论业务解决(返回、解决其它逻辑)}
}
}
lock.tryLock()
: 阻塞式获取锁,如果可能获取则返回 true 否则 返回 false。无奈获取也能够依据理论业务进行解决。
案例上手
synchronized 案例
public class LockExample1 {
// 申请总数
public static int requestTotal = 10000;
// 并发计数
public static int count = 0;
public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
for (int i = 0; i < requestTotal; i++) {executorService.execute(() -> {
try {add();
} catch (Exception e) { }
countDownLatch.countDown();});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count =" + count);
}
private static synchronized void add() {count++;}
}
// 运行后果:count = 10000
给 add()
办法加上了 synchronized
锁,保障了该办法在并发下也是同步的。
lock() 办法的应用
public class LockExample2 {
// 申请总数
public static int requestTotal = 10000;
// 并发计数
public static int count = 0;
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
for (int i = 0; i < requestTotal; i++) {executorService.execute(() -> {
try {add();
} catch (Exception e) { }
countDownLatch.countDown();});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count =" + count);
}
private static void add() {lock.lock();
try {count++;} finally {lock.unlock();
}
}
}
// 运行后果:count = 10000
将须要同步的代码放在 lock
和 unlock
之间,应用 lock
肯定要记得开释锁。
tryLock() 办法
private static void add() {if (lock.tryLock()) {
try {count++;} finally {
// 当获取锁胜利时最初肯定要记住 finally 去敞开锁
lock.unlock(); // 开释锁}
} else {
//else 时为未获取锁,则无需去敞开锁
// 如果不能获取锁,则间接做其余事件
System.out.println(Thread.currentThread().getName() + "没有获取锁");
}
}
通过 tryLock()
办法就发现在并发的状况下会有局部线程无奈获取到锁。
tryLock(long timeout, TimeUnit unit) 能够设置超时工夫
private static void add() throws InterruptedException {if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
try {count++;} finally {
// 当获取锁胜利时最初肯定要记住 finally 去敞开锁
lock.unlock(); // 开释锁}
} else {
//else 时为未获取锁,则无需去敞开锁
// 如果不能获取锁,则间接做其余事件
System.out.println(Thread.currentThread().getName() + "没有获取锁");
}
}
ReentrantLock 提供了偏心和非偏心锁的实现
- 偏心锁:
ReentrantLock lock = new ReentrantLock(true)
。是指多个线程在期待同一个锁时,必须依照申请锁的工夫程序来顺次取得锁。 - 非偏心锁:
ReentrantLock lock = new ReentrantLock(false)
。如果构造函数不传递参数,则默认是非偏心锁。
欢送关注公众号 山间木匠, 我是小春哥,从事 Java 后端开发,会一点前端、通过继续输入系列技术文章以文会友,如果本文能为您提供帮忙,欢送大家关注、点赞、分享反对,_咱们下期再见!_