为什么多线程很重要?
- 硬件:摩尔定律失效,电路上的元器件数目每隔18个月增加一倍,可从2003年开始CPU主频不再翻倍,而是采用多核而不是更快的主频。在核数不断增加情况下,要想程序更快就要用到并行
- 软件:高并发系统,异步执行需要用到多个线程
什么是进程与线程、管程?
每个进程代表一个程序,拥有自己的内存空间和系统资源
一个进程里有多个线程,可以执行多个任务
Monitor管程对象,每个对象实例都会有一个Monitor对象
什么是用户线程与守护线程?
守护线程在后台默默完成一些服务,例如垃圾回收器,当用户线程结束了,不管守护线程是否执行结束,守护线程也会随之退出。Thread.setDaemon(true),但应该在start()之前设置才能生效
用户线程是工作线程,完成分配的业务操作。
Future
FutureTask<Integer> futureTask = new FutureTask(()->{
Thread.sleep(1000);
System.out.println("子任务执行结束");
return 1;
});
new Thread(futureTask).start();
System.out.println(futureTask.get(2, TimeUnit.SECONDS));
System.out.println("----main执行结束----");
子任务执行结束
1
----main执行结束----
Future.get()会进行阻塞等待结果
CompletableFuture(异步任务编排) api文档
语法示例:
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
//业务处理
int num = 1 + 1;
return num;
}).thenApply(num -> {
//根据上面的结果,进行后续的处理
int num2 = num * 2;
return num2;
}).thenApplyAsync(num2 -> {
//拿到上面的结果后,又开启新的异步线程开始执行了
int num3 = num2 + 1;
return num3;
}).whenComplete((num3, e) -> {
if (e == null) {
System.out.println(MessageFormat.format("计算结果:{0}", num3.toString()));
}
}).exceptionally(e -> {
e.printStackTrace();
return null;
}).join();
System.out.println("---main---");
}
计算结果:5
---main---
CompletableFuture.join()不抛异常
CompletableFuture.get()抛异常
通过一个案例实测一下,假设我需要根据一个文件地址,模拟计算出所有子目录或文件的总大小
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import java.io.File;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CompletableFutureTest {
public static void main(String[] args){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), 10, 1, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(50));
StopWatch time = new StopWatch();
time.start("获取所有文件");
List<File> files = getFiles("C:\\Program Files\\Java\\jdk1.8.0_181\\jre");
time.stop();
time.start("计算所有文件");
Long size = calcFile(files);
time.stop();
System.out.println(MessageFormat.format("找到文件:{0}个,计算结果:{1}B",files.size(),size.toString()));
time.start("文件分批");
List<List<File>> lists = listSplitBySize(files, 20);
time.stop();
time.start("CompletableFuture计算所有文件");
Long size1 = calcFileWithCompletableFuture(lists,threadPoolExecutor);
System.out.println(MessageFormat.format("找到文件:{0}个,CompletableFuture计算结果:{1}B",files.size(),size1.toString()));
time.stop();
System.out.println(time.prettyPrint(TimeUnit.MILLISECONDS));
threadPoolExecutor.shutdown();
}
@SneakyThrows
private static List<File> getFiles(String path){
File file = FileUtil.file(path);
List<File> result = Lists.newArrayList();
if(file.isDirectory()){
File[] files = FileUtil.ls(FileUtil.getAbsolutePath(file));
Arrays.stream(files).forEach(f->result.addAll(getFiles(FileUtil.getAbsolutePath(f))));
}else {
result.add(file);
}
return result;
}
private static Long calcFileWithCompletableFuture(List<List<File>> fileList,Executor executor){
return fileList.stream().map(files -> CompletableFuture.supplyAsync(() -> {
Long calcResult = calcFile(files);
return calcResult;
},executor)).collect(Collectors.toList()).stream().mapToLong(CompletableFuture::join).sum();
}
private static Long calcFile(List<File> files){
return files.stream().mapToLong(file -> {
//假设这里对每个文件进行其他读取或处理耗时
try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
return FileUtil.size(file);
}).sum();
}
public static <T> List<List<T>> listSplitBySize(List<T> data,Integer size){
long step = (data.size() + size - 1) / size;
return Stream.iterate(0, n -> n + 1).limit(step).parallel().map( n -> data.stream().skip(n * size).limit(size).parallel().collect(Collectors.toList())).collect(Collectors.toList());
}
}
找到文件:241个,计算结果:209036999B
找到文件:241个,CompletableFuture计算结果:209036999B
StopWatch '': running time = 4631 ms
---------------------------------------------
ms % Task name
---------------------------------------------
000000233 05% 获取所有文件
000003730 81% 计算所有文件
000000014 00% 文件分批
000000653 14% CompletableFuture计算所有文件
锁
- 1.悲观锁:先加锁,再读写数据。例:synchronized和Lock实现类
- 2.乐观锁:认为自己是使用数据时,别的线程不会对其修改,只是在更新数据的时候判断有没有别的线程更新了这个数据。例:CAS
public void test(){
Object obj = new Object();
synchronized (obj){
System.out.println("执行中...");
}
}
如果synchronized是代码块形式,在字节码中会出现1次monitorenter和2次monitorexit指令,但是如果是synchronized代码块中出现异常,字节码只会出现1次monitorenter和1次monitorexit指令.
public void test(){
Object obj = new Object();
synchronized (obj){
System.out.println("执行中...");
throw new RuntimeException("出现异常");
}
}
- 3.公平锁: 线程判断队列中是否有线程排队,如果有插入队尾,没有就CAS设置成功标识的视为拿到锁。 (雨露均沾)
- 4.非公平锁: 线程通过CAS先设置成功标识的视为拿到锁。获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。 (效率优先)
- 5.可重入锁: 如果是当前线程持有,则对次数进行+1,解锁-1,有效避免了死锁。
死锁示例:
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
new Thread(()->{
synchronized (o1){
System.out.println("1-拿到了o1锁");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("1-拿到了o2锁");
}
}
}).start();
new Thread(()->{
synchronized (o2){
System.out.println("2-拿到了o2锁");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("2-拿到了o1锁");
}
}
}).start();
}
1-拿到了o1锁
2-拿到了o2锁
怎么排查证明发生了死锁?
jstack <pid>
- 6.读写锁:能被多个读线程访问,或者被一个写线程访问,不能同时读写访问。例如:ReentrantReadWriteLock
缺点:锁饥饿,一直读,写抢占不到锁。
ReentrantReadWriteLock的锁降级:同一线程中,写锁中获得读锁,然后再释放写锁;写锁降级为---->读锁, 其他线程就可以立即读取,而其他线程的写锁只能等待读完。 但是读锁不能变成写锁! - 7.邮戳锁:StampedLock,不可重入锁,它是对ReentrantReadWriteLock的优化,读的过程中也可以写。
尝试乐观读,返回一个版本号,最后验证版本号是否匹配,如果不匹配就会切换到悲观读的模式。
StampedLock stampedLock = new StampedLock();
long version = stampedLock.tryOptimisticRead();
System.out.println("进行乐观读");
if(!stampedLock.validate(version)){
long readLock = stampedLock.readLock();
try{
System.out.println("切换悲观读模式,重新获取数据");
}finally{
stampedLock.unlockRead(readLock);
}
}
线程的中断
-
Thread.stop():强制中断线程,相当于kill -9,不推荐,容易造成数据不一致 - volatile+标记位:
static volatile boolean stopFlag = false;
public static void main(String[] args) {
new Thread(() -> {
while (true){
if(stopFlag){
System.out.println(Thread.currentThread().getName()+"中断");
break;
}else{
System.out.println(Thread.currentThread().getName()+"执行中...");
}
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
},"线程1-").start();;
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stopFlag = true;
System.out.println(Thread.currentThread().getName()+"执行完毕");
},"线程2-").start();
}
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程2-执行完毕
线程1-中断
线程1-执行完毕
- AtomicBoolean原子类:
static AtomicBoolean stopFlag = new AtomicBoolean(false);
public static void main(String[] args) {
new Thread(() -> {
while (true){
if(stopFlag.get()){
System.out.println(Thread.currentThread().getName()+"中断");
break;
}else{
System.out.println(Thread.currentThread().getName()+"执行中...");
}
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
},"线程1-").start();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stopFlag.set(true);
System.out.println(Thread.currentThread().getName()+"执行完毕");
},"线程2-").start();
}
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程2-执行完毕
线程1-中断
线程1-执行完毕
- Thread.isInterrupted() + Thread.interrupt();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
//isInterrupted()判断当前线程是否被中断,默认是false。
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "中断");
break;
} else {
System.out.println(Thread.currentThread().getName() + "执行中...");
}
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}, "线程1-");
thread1.start();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将isInterrupted()标记置为true。
thread1.interrupt();
System.out.println(Thread.currentThread().getName()+"执行完毕");
},"线程2-").start();
}
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程2-执行完毕
线程1-中断
线程1-执行完毕
interrupt()不能真正的中断线程,只是设置了中断标记,真正的中断交给线程自己去控制。
如果线程处于阻塞状态下(sleep、wait、join)会抛出InterruptedException,线程中断标记依旧是false,需要在异常块中再次Thread.currentThread().interrupt();标记才会置为true。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
线程的等待与唤醒
- 1.wait()与notify()。必须要在synchronized代码块中,并且对顺序有要求。
public static void main(String[] args) {
Object obj = new Object();
new Thread(()->{
synchronized (obj){
System.out.println(Thread.currentThread().getName()+":开始了");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":结束了");
}
},"线程1").start();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println(Thread.currentThread().getName()+":开始了");
obj.notify();
System.out.println(Thread.currentThread().getName()+":结束了");
}
},"线程2").start();
}
线程1:开始了
线程2:开始了
线程2:结束了
线程1:结束了
- 2.Condition.await()与signal(),必须要在Lock.lock()代码块中,同样有顺序要求。
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+":开始了");
condition.await();
System.out.println(Thread.currentThread().getName()+":结束了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"线程1").start();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+":开始了");
condition.signal();
System.out.println(Thread.currentThread().getName()+":结束了");
} finally {
lock.unlock();
}
},"线程2").start();
}
线程1:开始了
线程2:开始了
线程2:结束了
线程1:结束了
- 3.LockSupport.park()与LockSupport.unpark(Thread)。 unpark会释放一个许可,多次调用unpark最高也只会是一个许可. park()获取一个许可; 并且没有顺序要求。
JMM(Java内存模型)
CPU的运算速度超过物理主内存的速度,所以CPU中设计了多级缓存;CPU的运行并不是直接操作物理内存,而是先把物理内存的数据读取到CPU高速缓存中,内存的读写就会造成数据不一致问题。
Java内存模式(java memory model)就是用于屏蔽硬件和操作系统的内存访问差异,仅仅是一种约定,围绕多线程的原子性,可见性,有序性。
- 可见性:当一个线程修改了某个共享变量,其他线程是否能立即知道该变更,JMM规定所有变量都存储在主内存中。
- 有序性:为了提升性能,编译器和处理器会对指令进行重排序,指令重排可以保证串行语义一致,但无法保证多线程间的语义也一致。
- 原子性:一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰。
- happens-before原则:一个操作先行发生另一个操作,那么第一个操作的执行结果将对第二个操作可见,且第一个操作的执行顺序排在第二个操作之前;如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
volatile
保证了可见性和有序性,但是不保证原子性,写内存会立即刷新回主内存中,读内存直接从主内存中读取;它是通过内存屏障指令达到的;对于一写多读可以解决变量的同步,多写就无法保证线程安全
内存屏障之前所有写操作都要写回主内存
内存屏障之后所有读操作都能获取写操作的最新结果
CPU内存屏障指令:loadload、storestore、loadstore、storeload
CAS
compare and swap:比较并替换,有3个重要操作参数(内存地址、旧值、新值),实现由CPU指令cmpxchg
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//CAS自旋直到修改成功
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
缺点:①CAS失败了会一直重试,造成CPU空转。
②ABA问题,值没变,但是被替换操作过;可以使用版本号,例如:AtomicStampedReference<V>
AtomicIntegerFieldUpdater
原子更新对象中int类型字段的值,缩小锁的范围,需要更新的属性字段必须添加volatile关键字
@Data
class User{
private String name;
private volatile int age;
private AtomicIntegerFieldUpdater ageFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public void birthday(User user){
ageFieldUpdater.getAndIncrement(user);
}
}
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) throws InterruptedException {
User user = new User();
user.setName("张三");
for (int i = 0; i < 30; i++) {
new Thread(()->{
user.birthday(user);
}).start();
}
Thread.sleep(1000);
System.out.println(String.format("user:%s,age:%d",user.getName(),user.getAge()));
}
}
user:张三,age:30
AtomicReferenceFieldUpdater
原子更新对象中引用类型字段的值
例如:多线程调用某个对象的初始化方法,该方法只能执行一次
@Data
class User{
private String name;
private volatile Boolean initFlag = false;
private AtomicReferenceFieldUpdater<User,Boolean> initFlagFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class,Boolean.class,"initFlag");
public void init(User user){
if(initFlagFieldUpdater.compareAndSet(user,false,true)){
System.out.println("初始化完成");
}
}
}
public class AtomicReferenceFieldUpdaterTest {
public static void main(String[] args) throws InterruptedException {
User user = new User();
for (int i = 0; i < 30; i++) {
new Thread(()->{
user.init(user);
}).start();
}
Thread.sleep(1000);
}
}
初始化完成
LongAdder
优点:对高并发场景下,吞吐量明显提升
缺点:只能从0开始加法运算
为什么比AtomicLong快?
class LongAdder extends Striped64
abstract class Striped64{
//cpu的数量,Cell[]的最大长度
static final int NCPU = Runtime.getRuntime().availableProcessors();
//2的幂等
transient volatile Cell[] cells;
//基础value值,并发低时CAS更新该值
transient volatile long base;
//新建/扩容Cell[]时使用的锁标记
transient volatile int cellsBusy;
}
思想:先casBase,修改失败了,创建长度为2的Cell[]数组,通过线程id进行hash,通过hash值位运算得到数组下标, 将value值分散到数组下标中,不同的线程命中数组的不同槽中进行自旋CAS操作,降低冲突的概率,如果所有Cell也CAS修改失败则进行2的幂次扩容,但不会超过CPU核心数,最后需要获取值就将(槽中的值汇总+base)返回。(返回的值不是强一致性,而是最终一致性,sun()没有加锁,获取的同时有可能还有线程在访问)
LongAccumulator
对LongAdder的扩展,提供了自定义函数操作,可以加减乘除等运算。
原子操作性能对比
public class LongAdderTest {
private static Long num = 0l;
private static Lock lock = new ReentrantLock();
private static AtomicLong atomicLong = new AtomicLong();
private static LongAdder longAdder = new LongAdder();
private static LongAccumulator longAccumulator = new LongAccumulator(((left, right) -> left+right),0);
private static synchronized void addSynchronized(){
num++;
}
private static void addLock(){
lock.lock();
try {
num++;
}finally {
lock.unlock();
}
}
private static void addAtomicLong(){
atomicLong.incrementAndGet();
}
private static void addLongAdder(){
longAdder.add(1);
}
private static void addLongAccumulator(){
longAccumulator.accumulate(1);
}
private static CountDownLatch c1 = new CountDownLatch(10);
private static CountDownLatch c2 = new CountDownLatch(10);
private static CountDownLatch c3 = new CountDownLatch(10);
private static CountDownLatch c4 = new CountDownLatch(10);
private static CountDownLatch c5 = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
StopWatch time = new StopWatch();
time.start("synchronized计数耗时");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
for (int j = 0; j < 10000000; j++) {
addSynchronized();
}
}finally {
c1.countDown();
}
}).start();
}
c1.await(1,TimeUnit.MINUTES);
System.out.println("synchronized计数结果:"+num);
num=0l;
time.stop();
time.start("ReentrantLock计数耗时");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
for (int j = 0; j < 10000000; j++) {
addLock();
}
}finally {
c2.countDown();
}
}).start();
}
c2.await(1,TimeUnit.MINUTES);
System.out.println("ReentrantLock计数结果:"+num);
time.stop();
time.start("AtomicLong计数耗时");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
for (int j = 0; j < 10000000; j++) {
addAtomicLong();
}
}finally {
c3.countDown();
}
}).start();
}
c3.await(1,TimeUnit.MINUTES);
System.out.println("AtomicLong计数结果:"+atomicLong.get());
time.stop();
time.start("LongAdder计数耗时");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
for (int j = 0; j < 10000000; j++) {
addLongAdder();
}
}finally {
c4.countDown();
}
}).start();
}
c4.await(1,TimeUnit.MINUTES);
System.out.println("LongAdder计数结果:"+longAdder.longValue());
time.stop();
time.start("LongAccumulator计数耗时");
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
for (int j = 0; j < 10000000; j++) {
addLongAccumulator();
}
}finally {
c5.countDown();
}
}).start();
}
c5.await(1,TimeUnit.MINUTES);
System.out.println("LongAccumulator计数结果:"+longAccumulator.longValue());
time.stop();
System.out.println(time.prettyPrint(TimeUnit.MILLISECONDS));
}
}
synchronized计数结果:100000000
ReentrantLock计数结果:100000000
AtomicLong计数结果:100000000
LongAdder计数结果:100000000
LongAccumulator计数结果:100000000
StopWatch '': running time = 9008 ms
---------------------------------------------
ms % Task name
---------------------------------------------
000003961 44% synchronized计数耗时
000002639 29% ReentrantLock计数耗时
000001921 21% AtomicLong计数耗时
000000196 02% LongAdder计数耗时
000000289 03% LongAccumulator计数耗时
ThreadLocal
提供线程局部变量,每个线程都有自己独立的变量副本,避免了多线程争抢共享变量不安全问题。
使用try-finally进行remove()回收,尤其在线程池场景下,线程会被重复使用,不清理ThreadLocal变量会造成业务逻辑问题以及内存泄漏问题
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
}
}
}
Thread.threadLocals = new ThreadLocal.ThreadLocalMap(ThreadLocal, value);
设置threadLocal变量,就是将threadLocal作为key,值为value的Entry对象放到了ThreadLocalMap中。
为什么ThreadLocalMap存放Entry的key是弱引用?
当ThreadLocal对象没有任何强引用的时候,触发GC后,ThreadLocal对象就会被回收掉,并且Entry对象的key为null。 如果变成强引用,那么发生GC时ThreadLocalMap还持有ThreadLocal的强引用,只要线程一直存在,ThreadLocalMap就一直不会被回收,从而导致ThreadLocal对象内存泄漏。
ThreadLocal的内存泄漏
public class ThreadLocalTest {
@Data
@AllArgsConstructor
@NoArgsConstructor
static class User{
private String name;
@Override
protected void finalize() throws Throwable {
System.out.println("User对象被GC回收了...");
}
}
private static ThreadLocal<User> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set(new User("张三"));
System.out.println("对象:"+threadLocal.get());
//threadLocal.remove();
threadLocal = null;
System.gc();
Thread.sleep(1000);
//Thread thread = Thread.currentThread();
//System.out.println(thread);
}
}
对象:ThreadLocalTest.User(name=张三)
如果这个线程迟迟不结束(尤其在线程池场景下),就无法释放一系列强关联的Entry Value对象。所以如果不使用某个ThreadLocal对象一定要调用remove()删除,防止内存泄漏。
synchronized
jdk5只有重量级锁,jdk6开始优化了synchronized锁升级过程。
- 偏向锁(默认开启):
-XX:-UseBiasedLocking(关闭偏向锁,如果是高并发场景,可以选择关闭,直接升级到轻量级锁)
通过CAS更新对象头中持有锁线程ID和锁标记,下一次进来,会判断当前线程是否与对象头持有锁线程ID相等,相等就直接进入同步代码块中,不需要每次都加锁,只有其他线程争抢偏向锁标记,持有偏向锁的线程才会释放,其他线程CAS锁标记失败,会等待全局安全点,检查持有偏向锁的线程是否在运行中,然后升级为轻量级锁。
- 轻量级锁:
就是通过CAS自旋进行争抢对象头锁标记,每次退出同步块都会释放锁,当自旋达到一定次数后(jdk6后是自适应动态的)升级为重量级锁。
- 重量级锁:
效率低下,monitor依赖底层操作系统的mutex lock(互斥锁)来实现的,挂起和恢复线程都需要操作系统内核态与用户态来回切换。
AQS
抽象了和锁交互的api,隐藏了实现细节,不需要关心锁等待和释放逻辑。
public abstract class AbstractQueuedSynchronizer{
//头节点引用
private transient volatile AbstractQueuedSynchronizer.Node head;
//尾节点引用
private transient volatile AbstractQueuedSynchronizer.Node tail;
//锁标识 0表示没有线程使用,1表示有线程使用中
private volatile int state;
//当前正在使用中的线程对象
private transient Thread exclusiveOwnerThread;
static final class Node {
volatile Node prev;
volatile Node next;
volatile Thread thread;
volatile int waitStatus;
}
}