Java相关文章
- Java内存模型
- Java中String特性
- Java对象内存布局
- JVM结构
- JVM垃圾回收器
- Java19虚拟线程新特性
- Java线程生命周期与常见方法
- Java线程池笔记
- 浅谈synchronized锁原理
AQS底层原理
- AQS核心思想
- AQS内部维护了一个核心变量state和两种队列:主队列(Main Queue)、条件队列(Condition Queue)
- 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
- 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到主队列中竞争锁。
- 如果被请求的共享资源满足条件队列的阻塞条件则进入条件队列中阻塞。
- 内部结构
- 维护了一个volatie int state的共享变量和一个先进先出的队列queue
- AQS定义了两种资源共享方式
- 独占模式
- 只有一个线程能获取到共享变量
- 以ReentrantLock为例,当state变量初始值为0,表示未锁定状态。
- 线程A访问的时候,state+1,就代表该线程锁定了共享资源,其他线程将无法访问
- 而当线程A访问完共享资源以后,state-1,直到state等于0,就将释放对共享变量的锁定,其他线程将可以抢占式或者公平式争夺。
- 当然,ReentrantLock支持可重入,那什么是可重入呢?同一线程可以重复锁定共享资源,每锁定一次state+1,也就是锁定多次。说明:锁定多少次就要释放多少次
- 共享模式
- 共享,多个线程可以同时执行,如 CountDownLatch、CyclicBarrier、Semaphore、ReadWriteLock
- 以CountDownLatch为例,共享资源可以被N个线程访问,也就是初始化的时候,state就被指定为N(N与线程个数相等),线程countDown()一次,state会CAS减1,直到所有线程执行完(state=0),那些await()的线程将被唤醒去执行执行剩余动作。
- 加锁步骤
同步队列与条件队列
- 同步队列存放着竞争同步资源的线程的引用(不是存放线程),而等待队列存放着待唤醒的线程的引用。
- 调用condition的await方法,将会使当前线程进入等待队列并释放锁(先加入等待队列再释放锁),同时线程状态转为等待状态
- 调用condition的signal方法时,将会把等待队列的首节点移到同步队列的尾部,然后唤醒该节点。
争抢资源,争抢失败进入同步队列中 - 以BlockingQueue为例
- 一般会有两个等待队列,用lock.newCondition()
- 一个用于队列满时阻塞插入线程的等待队列
- 一个用户队列为空时阻塞获取线程的等待队列
AQS有哪些实现
- ReentrantLock
- 独享
- CountDownLatch
- 线程计数器
- CountDownLatch是通过一个计数器来实现的,计数器的初始化值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就相应得减1。当计数器到达0时,表示所有的线程都已完成任务,然后在闭锁上等待的线程就可以恢复执行任务。
- 利用AQS调度共享模式,多个线程可以同时执行,共享资源可以被N个线程访问,也就是初始化的时候,state就被指定为N(N与线程个数相等),线程countDown()一次,state会CAS减1,直到所有线程执行完(state=0),那些await()的线程将被唤醒去执行执行剩余动作。
- CyclicBarrier
- CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
- CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行; 而
- CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
- CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。