以下内容来自于Java高级程序员面试笔试宝典
java.util.concurrent 包的核心类是AbstractQueueSychronizer,这是一个同步器+阻塞锁的基本架构,用于控制加锁和释放锁,并且在内部维护一个FIFO的线程的等待队列,包中的锁,屏障等同步器多数是靠它实现的
- 这是一个可以由线程以独占方式拥有的同步器,这个类为创建锁和相关同步器提供了基础
- 用虚拟队列的方式来管理线程中锁的获取与释放,同时也提供了各种情况下线程中断。这个类提供了默认的同步实现,但是获取锁和释放锁的实现被定义为抽象方法,由子类实现。这样做的目的是使得开发人员可以自由定义锁的获取与释放
- Sync是ReentrantLock的内部抽象类,实现了简单的锁的获取与释放
- Sync有两个子类NonfairSync和FairSync,分别表示了“非公平锁”和公平锁
-
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
- 非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
- 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
- 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
-
- https://blog.csdn.net/qq_35190492/article/details/104943579https://blog.csdn.net/qq_35190492/article/details/104943579
- AQS维护了一个volatile int state 和一个FIFO线程等待队列,使用双线连标实现的,当多线程争用资源被阻塞时会进入此队列。只有当head节点持有的线程释放了资源之后,下一份线程才能获取资源。在这个工作模型中,state即是AQS的同步状态量,也被称为资源。
AQS的同步状态关键字
- state是AQS非常重要的表述线程同步转台的成员变量:private volatile int state
- state用来表示“线程争夺的资源”。如果是0,那么说明没有线程正字等待资源,如果为n,那么说明有n个线程正在等待资源的释放
- 在AQS中,有三种访问方式:getState,setState,compareState
- 通过观察state的定义,可以注意到一个关键字volatile
- volatile关键字可以让state变量保持线程间的可见性和有序行,是保证线程安全的重要条件之一。
- AQS定义两种资源共享的方式:Exclusive(独占,在特定时间内,只有一个线程能够执行,如ReentrantLock)和Share(共享。在特定时间内,只有一个线程能够执行,如Semaphore/CountDownLatch)
volatile关键字
- volatile在线程安全场景下被广泛使用,以AQS中队列的head和tail为例子:
-
private transient volatile Node head; private transient volatile Node tail;
在AQS等待队列里,最新添加的节点node,会成为新的tail,添加的工程如下:
-
pre = tail node.prev = pre tail = node pre.next = node
这个过程让node成为了新的tail,同时对tail和node的next/pre的指向进行相应的修改,这样的操作在单线程下是没有问题的
-
但是,tail是一个成员变量,在多线程的情况下,步骤一和步骤三里的tail指向的可能不是同一个对象,因为tail可能被线程A使用的过程中被线程B修改,
-
在使用synchrinized进行线程同步的时候,只能让A或者B中的一个先执行完成,然后再让等待的线程继续执行,使用这种方法,虽然安全性有了保障,但是性能不佳。那么,要如何保证tail和node交换这个操作是线程安全,并且效率更高?
-
使用volatile
-
注意:volatile并不保证线程安全。
-
线程安全是指,在多线程的情况下,对共享内存的使用,不会因为不同线程的访问和修改而发生不期望的情况。
-
volatile的三个作用
-
用于解决多核cpu高速缓存导致的变量不同步:本质是个硬件问题,CPU的高速缓存(cache)的读取速度远远快于主存(物理内存。所以,CPU在读取一个变量的时候,会先把数据读取到缓存,这样下次再访问同一个数据的时候就可以直接从缓存中读取了,显然提高了读取的性能。而CPU有
-