0
点赞
收藏
分享

微信扫一扫

JUC包下的锁


五、JUC中的锁

在前面学习了Synchronized锁,回顾synchronized:


  1. 可重入锁。
  2. 锁升级:无锁态 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
  3. 非公平锁

公平锁和非公平锁:

当程序加锁时,肯定会有多个线程竞争这把锁,当一个线程获得锁后,那么就会有一个等待队列维护这些等待线程。

公平锁:线程遵循达到的先后顺序,先来的优先获取锁,后来的后获取锁。这样的话,内部就要维护一个有序队列

非公平锁:线程到达后直接参与竞争,如果得到锁直接执行,没有得到锁的话,就进入等待队列。


系统管理

因为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.



举报

相关推荐

0 条评论