可重入锁ReentranLock:
ReentranLock 显示锁 (lock.lock())
synchronizer 隐式锁
一个线程在持有一个锁的时候,它内部能否再次(多次)申请该锁。如果一个线程已经获得了锁,其内部还可以多次申请该锁成功。那么我们就称该锁为可重入锁。
void methodA(){
lock.lock(); // 获取锁
methodB();
lock.unlock() // 释放锁
}
void methodB(){
lock.lock(); // 获取锁
// 其他业务
lock.unlock();// 释放锁
}
可重入锁可以理解为锁的一个标识。该标识具备计数器功能。标识的初始值为0,表示当前锁没有被任何线程持有。每次线程获得一个可重入锁的时候,该锁的计数器就被加1。每次一个线程释放该所的时候,该锁的计数器就减1。
前提是:当前线程已经获得了该锁,是在线程的内部出现再次获取锁的场景
可重入锁机制:https://www.cnblogs.com/1013wang/p/11820373.html
为什么要使用LockSupport?
传统的等待唤醒机制
- 使用Object中的wait()方法让线程等待,使用Object中的notify方法唤醒线程
- 使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
- LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
Object类中wait()和notify()实现线程的等待唤醒
- wait和notify方法必须要在同步块或同步方法里且成对出现使用。 wait和notify方法两个去掉同步代码块后看运行效果出现异常情况:
- Exception in thread “A” Exception in thread “B”
- java.lang.IllegalMonitorStateException
- 先wait后notify才可以(如果先notify后wait会出现另一个线程一直处于等待状态)
- synchronized关键字是属于JVM层面。monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖monitor对象,只能在同步块或方法中才能调用wait/notify方法)
juc强大的三个工具类
CountDownLatch(闭锁)
- CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
- 其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
//需求:要求6个线程都执行完了,mian线程最后执行
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t");
countDownLatch.countDown();
},i+"").start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t班长关门走人,main线程是班长");
}
}
利用枚举减少if else的判断
public enum CountryEnum {
one(1,"齐"),two(2,"楚"),three(3,"燕"),
four(4,"赵"),five(5,"魏"),six(6,"韩");
private Integer retCode;
private String retMessage;
private CountryEnum(Integer retCode,String retMessage){
this.retCode=retCode;
this.retMessage=retMessage;
}
public static CountryEnum getCountryEnum(Integer index){
CountryEnum[] countryEnums = CountryEnum.values();
for (CountryEnum countryEnum : countryEnums) {
if(countryEnum.getRetCode()==index){
return countryEnum;
}
}
return null;
}
public Integer getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
}
```java / 楚 国,被灭 魏 国,被灭 赵 国,被灭 燕 国,被灭 齐 国,被灭 韩 国,被灭 main *秦国一统江湖
*/ public class CountDownLatchDemo { public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(6); for (int i = 1; i <=6; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t"+"**国,被灭"); countDownLatch.countDown(); },CountryEnum.getCountryEnum(i).getRetMessage()).start(); } countDownLatch.await(); System.out.println(Thread.currentThread().getName()+"\t"+"**秦国一统江湖");
} } ```
CyclicBarrier
CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法
//集齐7颗龙珠就能召唤神龙 public class CyclicBarrierDemo { public static void main(String[] args) { // public CyclicBarrier(int parties, Runnable barrierAction) {} CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{ System.out.println("召唤龙珠"); }); for (int i = 1; i <=7; i++) { final int temp=i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t收集到了第"+temp+"颗龙珠"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
Semaphore(信号量)
- acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
- release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
- 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore=new Semaphore(3); for (int i = 1; i <=6; i++) { new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+"\t抢占了车位"); semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"\t离开了车位"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); } },String.valueOf(i)).start(); } } }
LockSupport详解
- 什么是LockSupport?
- 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
- LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在仍以位置阻塞,阻塞之后也有对应的唤醒方法。 LockSupport调用的Unsafe中的native代码。
- 官网:
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
- 使用了名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可 , permit只有两个值 0和1, 默认是0
- 可以把许可看成是一种(0,1)信号量,但与Semaphore不同的是,许可的累加上限是1
阻塞方法
- permit默认是0,所以一开始调用park()方法, 当前线程就会阻塞,知道别的线程将当前线程permit的unpark方法 会被唤醒,然后会将permit再次设置为0并返回
- static void park(): 底层是unsafe类native方法
- static void park(Object blocker)
唤醒方法
- 调用unpark(thread)方法后,会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
- static void unpark()
LockSupport它能解决的痛点
- LockSupport不用持有锁块,不用加锁,程序性能好
- 先后顺序,不容易导致卡死(因为unpark活得一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,将线程释放)
有关面试:
为什么可以先唤醒线程后阻塞线程?
因为unpark活得一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,不会阻塞线程。
为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行
AbstractQueuedSynchronizer之AQS锁
AQS是什么?
- 用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石。通过内置的CLH(FIFO)队列的变种来完成资源获取线程的排队工作,将每条将要区抢占资源的线程封装成一个Node节点来实现锁的分配,有一个int类变量表示持有锁的状态,通过CAS完成对status值的修改(0表示没有,1表示阻塞)
- AQS为什么是JUC内容中最重要的基石
- ReentranLock
- CountDownLatch
- ReentrantReadWriteLock
- Semaphore
AQS内部架构图: