JUC包下的锁
五、JUC中的锁
在前面学习了Synchronized锁,回顾synchronized:
-
可重入锁。
-
锁升级:无锁态 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
-
非公平锁
公平锁和非公平锁:
当程序加锁时,肯定会有多个线程竞争这把锁,当一个线程获得锁后,那么就会有一个等待队列维护这些等待线程。
公平锁:线程遵循达到的先后顺序,先来的优先获取锁,后来的后获取锁。这样的话,内部就要维护一个有序队列
非公平锁:线程到达后直接参与竞争,如果得到锁直接执行,没有得到锁的话,就进入等待队列。
-
系统管理
因为synchronized的原语是
monitorenter
,获取锁和释放锁都是jvm通过加监控和退出监控实现的。
看下面这个题:
一个线程打印ABCDEFG,另一个线程打印abcdefg,控制两个线程交替打印AaBbCcDdEeFfGg。
首先来看使用Synchronized实现:
import java.util.concurrent.TimeUnit;
/**
* @author 赵帅
* @date 2021/1/13
*/
public class SynchronizedPrint {
private final Object lock = new Object();
public void fun1() {
String str = "ABCDEFG";
char[] chars = str.toCharArray();
synchronized (lock) {
for (char aChar : chars) {
System.out.print(aChar);
try {
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
}
public void fun2() {
String str = "abcdefg";
char[] chars = str.toCharArray();
synchronized (lock) {
for (char aChar : chars) {
System.out.print(aChar);
try {
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedPrint print = new SynchronizedPrint();
new Thread(print::fun1).start();
new Thread(print::fun2).start();
TimeUnit.SECONDS.sleep(1);
System.out.println("\n");
}
}
ReentrantLock
ReentrantLock是JUC包下的基于CAS实现的锁,因此是一个轻量级锁。查看ReentrantLock的构造方法可以发现ReentrantLock默认为非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock通过构造方法参数可以选择是公平锁或非公平锁,默认是非公平锁。
/** 创建公平锁 */
private final ReentrantLock fairLock = new ReentrantLock(true);
/** 创建非公平锁 */
private final ReentrantLock nonfairLock = new ReentrantLock();
ReentrantLock的使用
ReentrantLock在使用时需要手动的获取锁和释放锁:
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 赵帅
* @date 2021/1/13
*/
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void fun1() {
lock.lock();
try {
// do something
}finally {
lock.unlock();
}
}
}
ReentrantLock需要通过lock.lock()
方法获取锁,通过lock.unlock()
方法释放锁。而且为了保证锁一定能狗被释放,避免死锁的发生,一般获取锁的操作紧挨着try
而且finally
的第一行必须为释放锁操作。
ReentrantLock是可重入锁。
因为ReentrantLock是手动获取锁因此当锁重入时,每获取一次锁就要释放一次锁。
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 赵帅
* @date 2021/1/14
*/
public class ReentrantLockDemo1 {
private final ReentrantLock lock = new ReentrantLock();
public void fun1() {
lock.lock();
try {
// 锁重入
lock.lock();
try {
// do something
System.out.println("do something");
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
}
}
上面代码获取了两次锁,所以就需要手动的释放两次锁。
ReentrantLock的方法:
常用的方法有:
lock()
: 获取锁unlock()
: 释放锁tryLock()
:尝试获取锁并立即返回获取锁结果true/false
tryLock(long timeout,Timeunit unit)
: 延迟获取锁,在指定时间内不断尝试获取锁,如果在超时前获取到锁,则返回true
,超时未获取到锁则返回false
。会响应中断方法。lockInterruptibly()
: 获取响应中断的锁。isHeldByCurrentThread
: 当前线程是否获取锁。newCondition()
获取一个条件等待对象。
上述方法的实际使用如下:
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 赵帅
* @date 2021/1/13
*/
public class ReentrantLockTest {
private final ReentrantLock lock = new ReentrantLock();
@Test
public void fun1() {
System.out.println("lock与unlock");
lock.lock();
try {
System.out.println("获取锁");
} finally{
lock.unlock();
}
}
@Test
public void fun2() throws InterruptedException {
Thread thread = new Thread(() -> {
try {
lock.lockInterruptibly();
System.out.println("获取到可响应线程中断方法的锁");
// 模拟业务耗时
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("线程被中断");
} finally {
lock.unlock();
}
}, "thread-1");
thread.start();
// 等待线程thread-1启动
TimeUnit.MILLISECONDS.sleep(200);
// 中断thread-1线程,此时线程thread-1会抛出InterruptedException异常
System.out.println("中断thread-1");
thread.interrupt();
}
public void run3() {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "获取到锁");
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + "未获取到锁");
}
}
@Test
public void fun3() {
Thread thread1 = new Thread(this::run3, "thread-1");
Thread thread2 = new Thread(this::run3, "thread-2");
thread1.start();
thread2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run4() {
try {
if (lock.tryLock(3L, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获取锁");
TimeUnit.SECONDS.sleep(4);
} else {
System.out.println(Thread.currentThread().getName() + "未获取锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 如果获取锁成功需要释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
}
@Test
public void fun4() throws InterruptedException {
Thread thread1 = new Thread(this::run4, "thread-1");
Thread thread2 = new Thread(this::run4, "thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
ReentrantLock和synchronized的区别:
比较项 | ReentrantLock | synchronized |
---|---|---|
可重入 | 支持 | 支持 |
响应中断 | 同时支持响应中断lock.lockInterruptibly() ,不响应中断 lock.lock() |
不支持响应中断 |
可控性 | 手动控制加解锁 | 系统控制加解锁,人力不可控 |
尝试获取锁 | tryLock() ,立即响应结果 |
不支持 |
延迟获取锁 | tryLock(timeout,timeunit) 延迟超时获取锁 |
不支持 |
公平锁 | 支持公平锁和非公平锁 | 非公平锁 |
ReentrantLock如果使用不当,没有释放锁,就会造成死锁,而synchronized是由系统管理加锁和释放锁。
Condition
在学习synchronized时我们知道,一个锁会对应一个等待队列,可以通过wait()
和notify(),notifyAll()
方法来控制线程等待和唤醒。ReentrantLock同样也支持等待和唤醒,不过ReentrantLock可以通过newCondition()
来开启多个等待队列,也就是说ReentrantLock一把锁可以绑定多个等待队列。
condition方法
await()
: 等待状态,进入等待队列,等待唤醒,与Object的wait()
方法功能相同。await(long timeout,TimeUnit unit)
: 进入有时间的等待状态,被唤醒或等待超时自动唤醒,与Object的wait(long timeout,TimeUnit unit)
功能相同。signal()
: 唤醒一个等待队列的线程,与notify()
功能相同。singlaAll()
: 唤醒等待队列中的所有线程,与notifyAll()
功能相同。awaitUntil(Date deadline)
: 进入等待状态,到达指定时间后自动唤醒。awaitUninterruptibly():
进入不响应线程中断方法的等待状态。awaitNanos(long timeout)
: 进入纳秒级等待状态,xx纳秒后自动唤醒
使用Condition
import org.junit.Test;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 赵帅
* @date 2021/1/14
*/
public class ConditionTest {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void run1() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获取锁");
// 进入等待状态,响应中断,等待唤醒
condition.await();
// 进入有超时时间的等待状态,等待结束自动唤醒
condition.await(1L, TimeUnit.MICROSECONDS);
// 进入纳秒级等待状态,超时自动唤醒
condition.awaitNanos(100L);
// 进入不响应中断的等待状态,无法通过 thread.interrupt() 中断线程
condition.awaitUninterruptibly();
// 进入等待状态,指定结束等待的时间,到达时间后自动唤醒
condition.awaitUntil(Date.from(LocalDateTime.of(2021, 1, 14, 11, 45)
.atZone(ZoneId.systemDefault()).toInstant()));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
public void run2() {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "获取锁");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
@Test
public void fun1() throws InterruptedException {
Thread thread1 = new Thread(this::run1, "thread-1");
Thread thread2 = new Thread(this::run2, "thread-2");
Thread thread3 = new Thread(this::run2, "thread-3");
thread1.start();
thread2.start();
// 等待线程1进入 lock.awaitUninterruptibly()
TimeUnit.SECONDS.sleep(2L);
thread3.start();
thread1.join();
thread2.join();
thread3.join();
}
}
Object监视器和Condition的区别
对比项 | Object监视器 | condition |
---|---|---|
前置条件 | synchronized(obj) 获取锁 |
lock.lock() 获取锁,lock.newCondition() 获取Condition对象 |
调用方式 | obj.wait() |
condition.await() |
等待队列个数 | 1个 | 多个 |
当前线程释放锁进入等待状态 | 支持 | 支持 |
当前线程释放锁进入超时等待状态 | 支持 | 支持 |
当前线程释放锁进入等待状态不响应中断 | 不支持 | 支持 |
当前线程释放锁进入等待状态到将来某个时间 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的所有线程 | 支持 | 支持 |
使用Condition实现阻塞队列BlockingQueue
import com.sun.org.apache.bcel.internal.generic.NEW;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 赵帅
* @date 2021/1/14
*/
public class BlockingQueue<T> {
private final ReentrantLock lock = new ReentrantLock();
private final LinkedList<T> list = new LinkedList<>();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final int size;
public BlockingQueue(int size) {
this.size = size;
}
/**
* 入队,如果队列不为空则阻塞。
*/
public void enqueue(T t) {
lock.lock();
try {
while (list.size() == size) {
notFull.await();
}
System.out.println("入队:" + t);
list.add(t);
notEmpty.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T dequeue() {
lock.lock();
try {
while (list.size() == 0) {
notEmpty.await();
}
T t = list.removeFirst();
System.out.println(Thread.currentThread().getName() + ":出队:" + t);
notFull.signalAll();
return t;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return null;
}
public static void main(String[] args) {
BlockingQueue<String> queue = new BlockingQueue<>(5);
// 生产者
new Thread(() -> {
for (int i = 0; i < 100; i++) {
queue.enqueue("str" + i);
}
}, "produce-1").start();
// 消费者
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
queue.dequeue();
}
}, "consumer-" + i).start();
}
}
}
使用ReentrantLock来实现输出AbBb...这道题:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 赵帅
* @date 2021/1/16
*/
public class ReentrantLockPrint {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void print(char[] array) {
lock.lock();
try {
for (char c : array) {
System.out.println(c);
condition.signal();
condition.await();
}
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
char[] array1 = "ABCDEFG".toCharArray();
char[] array2 = "abcdefg".toCharArray();
ReentrantLockPrint demo = new ReentrantLockPrint();
new Thread(() -> demo.print(array1)).start();
new Thread(() -> demo.print(array2)).start();
TimeUnit.SECONDS.sleep(2);
}
}
CountDownLatch
countDownLatch,也叫门栓。使用来等待线程执行完毕的锁。方法如下:
countDownLatch在创建时需要指定一个count值,表示需要等待完成的线程数。
await()
: 使当前线程进入等待状态await(long timeout,TimeUnit unit)
: 使当前线程进入超时等待状态,超时自动唤醒线程。countDown()
:使count值减1,当count的值为0时,唤醒等待状态的线程。getCount
: 获取当前的count值。
使用如下:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @author 赵帅
* @date 2020/8/5
*/
public class CountDownLatchDemo {
private static final CountDownLatch lock = new CountDownLatch(5);
private static void fun1() {
try {
System.out.println(Thread.currentThread().getName() + ":到达");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + ":放行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.countDown();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(CountDownLatchDemo::fun1, "t" + i).start();
TimeUnit.SECONDS.sleep(2);
}
lock.await();
System.out.println("111");
}
}
可以看到,countDownLatch
在初始化的时候,必须指定一个大小,这个大小可以理解为加了几道门拴。然后,当一个线程调用await()
方法后,那么当前线程就会等待。我们初始化countDownLatch(count)
时,指定了一个count,只有当这个count等于0的时候,等待才会结束,否则就会一直等待。每调用一次countDown()
方法,那么count值就减1。
上面的代码我们创建了一个初始化大小为5的countDownLatch,可以为创建了一个门拴,上面有5道锁。然后在main方法中又创建了5个线程,main方法调用了await()
方法等待count变成0。然后创建的5个线程,每个线程都会在执行结束时,调用countDown()
来将当前的门栓减去1,当所有的线程执行结束,count值变为0。那么main方法就可以继续执行了。
CountDownLatch
主要就是用来等待线程执行结束的。在之前我们都是使用thread.join()
方法来将线程加入当前线程来保证线程运行结束,但是这种写法会非常麻烦,如果线程多我们就要写好多遍join。countDownLatch的作用与join方法相同。
可以理解为:打仗时,有很多个战斗小队,这些战斗小队就是一个个的线程。然后路上遭路雷区,拦住了所有的队伍(调用了
await()
方法的线程)。这些队伍就停下来,进入等待的状态了。然后就要派人去排雷,每排掉一颗雷。雷的总数就减1。这样当地雷全部排完,队伍才可以继续往下执行。地雷排完之前,队伍都是处于原地等待状态。
CyclicBarrier
CyclicBarrier也叫栅栏。与CountDownLatch很相似,都是使线程在这里等待,创建时都需要指定一个int参数parties,表示这个栅栏要拦的线程总数。方法如下:
await()
: 进入等待状态,并将线程数count加1,当count==parties时,唤醒所有的等待线程。await(long timeout,TimeUnit unit)
: 进入超时等待状态,超时自动唤醒。getParties()
: 获取这个栅栏的大小,即初始化时指定的大小。getNumberWaiting()
: 获取目前处于等待状态的线程数。reset()
: 重置栅栏,将唤醒所有等待状态的线程。后面来的线程将重新开始计数。
CyclicBarrier的用法如下:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* @author 赵帅
* @date 2020/8/6
*/
public class CyclicBarrierDemo {
private static final CyclicBarrier barrier = new CyclicBarrier(5);
public static void fun1() {
System.out.println(Thread.currentThread().getName() + ":线程到达");
try {
barrier.await();
System.out.println(Thread.currentThread().getName() + ":释放锁");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(CyclicBarrierDemo::fun1, "t" + i).start();
TimeUnit.SECONDS.sleep(1);
}
}
}
CyclicBarrier还可以在创建时指定最后一个线程达到时执行某个方法:
private static final CyclicBarrier barrier = new CyclicBarrier(5,()->{
System.out.println(Thread.currentThread().getName() + ":最后到达");
});
来看看CyclicBarrier的使用。首先我们创建时也需要指定一个线程数大小,然后还可以指定一个调用函数。创建成功后内部会有一个数维护当前等待的线程数。初始为0,在使用时通过await()
方法进入等待时,就将这个数+1,当进入等待状态的线程数与CyclicBarrier指定的线程数相等时就唤醒所有等待的线程,并将等待的线程数清0,开始新一轮的拦截。
通俗理解就是:打仗时需要撤退,不能乱撤,得听命令,然后就有一个栅栏拦着,所有撤退的人都得等待栅栏打开你才能出。而且撤退都是分批的。不能说一群人一块冲出去,因此就编队。我这个栅栏一次只能出去五个人,人要走前先得来栅栏前面占着位置等着。等凑够5个人了,我就打开你们出去,我等下一批五个人。当创建时指定一个构造方法时,这个构造方法只有最后一个线程到达后会执行,可以理解为:我这个栅栏有一个开关控制,最后一个人过来时,你得先来我这打开开关你才能走。
CyclicBarrier与CountDownLatch的区别
- CyclicBarrier是可以重复使用的,当线程数满了后会自动清0。countDownLatch是一次性的,当数减为0后,就失效了。
- CyclicBarrier可以指定最后一个线程到达时执行一个方法。
当调用线程中断时会
Semaphore
Semaphore又叫信号量,使用方式如下:
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author 赵帅
* @date 2020/8/6
*/
public class SemaPhoreDemo {
private static final Semaphore semaphore = new Semaphore(5);
public static void fun1() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + ":获取令牌");
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + ":释放令牌");
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(SemaPhoreDemo::fun1, "t" + i).start();
TimeUnit.SECONDS.sleep(1);
}
}
}
Samephore理解起来就好像一个房子,门口有保安,一个人想要进去,需要拿一个令牌,领牌的数量是有限的,令牌发完后,后来的人要在门口等着,等里面的人出来会归还领牌,这时等着的人就可以拿领牌进去了。
因此,samephore比较适合拿来做限流。
Phaser
phaser翻译过来是阶段,它维护程序执行的一个阶段一个阶段的。
import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
/**
* @author 赵帅
* @date 2021/1/16
*/
public class PhaserDemo {
private static Phaser phaser = new SimplePhaser();
private static class SimplePhaser extends Phaser {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("===============阶段" + phase + "总人数" + registeredParties);
switch (phase) {
case 0:
return false;
case 1:
return false;
case 2:
return false;
case 3:
return true;
default:
return true;
}
}
}
private static class Person implements Runnable {
private final Random random = new Random();
private String name;
public Person(String name) {
this.name = name;
}
private void sleepRandom() {
try {
TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void arrive() {
sleepRandom();
System.out.println(name + "到达");
phaser.arriveAndAwaitAdvance();
}
private void marry() {
sleepRandom();
System.out.println(name + "开始结婚");
phaser.arriveAndAwaitAdvance();
}
private void eat() {
sleepRandom();
System.out.println(name + "开始吃饭");
phaser.arriveAndAwaitAdvance();
}
private void leave() {
sleepRandom();
if ("新郎".equals(name) || "新娘".equals(name)) {
phaser.arriveAndAwaitAdvance();
} else {
System.out.println(name + "吃完饭走");
phaser.arriveAndDeregister();
}
}
@Override
public void run() {
arrive();
marry();
eat();
leave();
}
}
public static void main(String[] args) {
phaser.bulkRegister(7);
for (int i = 0; i < 5; i++) {
new Thread(new Person("路人" + i)).start();
}
new Thread(new Person("新郎")).start();
new Thread(new Person("新娘")).start();
}
}
ReadWriteLock
readwritelock,读写锁,可以拆分为读锁和写锁,读锁时共享锁,写锁时排他锁。用法如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author 赵帅
* @date 2020/8/6
*/
public class ReadWriteLockDemo {
public static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static final Lock readLock = readWriteLock.readLock();
private static final Lock writeLock = readWriteLock.writeLock();
/**
* 读锁
*/
public static void fun1() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + ":获取读锁");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName() + ":释放读锁");
}
}
public static void fun2() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + ":获取写锁");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + ":释放写锁");
}
}
public static void main(String[] args) {
new Thread(ReadWriteLockDemo::fun1, "t").start();
new Thread(ReadWriteLockDemo::fun1, "t1").start();
new Thread(ReadWriteLockDemo::fun1, "t2").start();
new Thread(ReadWriteLockDemo::fun2, "t3").start();
new Thread(ReadWriteLockDemo::fun2, "t4").start();
}
}
LockSupport
前面的锁都需要在锁内部等待或唤醒,lockSupport支持在锁的外部唤醒指定的锁。相比之下更加灵活,用法如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author 赵帅
* @date 2020/8/6
*/
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
LockSupport.park();
}
if (i == 8) {
LockSupport.park();
}
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
LockSupport.unpark(thread);
LockSupport.unpark(thread);
}
}
LockSupport可以在调用park()
方法前先调用unpark()
。LockSupport底层使用的是Unsafe类。
每一个线程都有一个许可
(permit)
,permit的值只有0和1。默认为0。当调用
unpark(thread)
方法时,会将线程的permit值设置为1,多次调用unpark
方法,permit的值还是1。当调用
park()
方法时:
- 如果当前线程的permit的值为1。那么就会将值变为0并立即返回。
- 如果当前线程的permit的值为0,那么当前线程就会被阻塞,并等待其他线程调用
unpark(thread)
将线程的值设为1,当前线程将被唤醒。然后会将permit的值设为0,并返回。
AQS
AQS全称为AbstractQueuedSynchronizer, ReentrantLock,CountDownLatch等都是通过AQS实现的。
AQS的核心是state属性,很多实现都是通过操作这个属性来实现的。而且AQS内部的方法都是操作unsafe的CAS操作实现的,因此说AQS的实现都是自旋锁。
ReentrantLock的实现:
ReentrantLock内部分为公平锁fairSync
和非公平锁NonfairSync
,这两个类都继承自Sync
,而sync
继承AQS, 当调用lock.lock()
获取锁时,查看lock()
方法的源码:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
当调用lock方法时,首先会尝试修改state属性的值,从0改成1。如果失败的话,那么就调用acquire方法。acquire方法内部调用了tryAcquire(arg)
方法,最终调用NonSync
的nonfairTryAcquire(arg)
方法,然后内部判断,如果当前的state值是0,那么就尝试将state的值设为1,如果设置1成功,则说明获取到锁,返回true,如果state的值不是0,但是加锁的线程是当前线程,也就是进入可重入锁了,那么就将state的值加1。
释放锁是,没释放一次就将state的值减1,最终保证state的值是0,那么这把锁就可以被其他线程使用了。
CountDownLatch的实现:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
public void countDown() {
sync.releaseShared(1);
}
CountDownLatch
在创建时就讲AQS的state的值设置为count值,然后每次调用countDown
方法就将state的值减1,知道state的值变为0.