volatile
作用:禁止JVM内存重排序(保证有序性);保证内存的可见性。
原理:1、通过插入内存屏障(Memory Barrier),在内存屏障前后禁止重排序优化,以此实现有序性 。
2、可见性的原理是 :内存屏障Load Barrier 和 Store Barrier即读屏障和写屏障,也可让高速缓存中的数据失效,强制从新从主内存加载数据;能让写入缓存中的最新数据更新写入主内存,其他线程程可见。另外volatile关键字修饰的变量会存在一个“lock:”的前缀,Lock会对CPU总线或高速缓存加锁。当需要使用个变量时,必须从主存中read–>load这个变量(即要使用这个变量时,必须从主存中读取这个变量,这就保证了该变量是最新的);当线程工作内存中这个变量被赋值时(assign),那么立刻store–>write这个变量(即当该值计算完成,立刻把这个变量写会主存,并且使得该值在其他内存的工作变量中无效)。
synchronized
作用:进行函数或代码块的加锁
原理:对象头的Markword存储类锁信息;在代码块修饰的原理是在在代码块开始插入monitorEnter,结束插入monitorExit;对函数的修饰是新增一个修饰ACC_SYNCHRONIZED;
详细原理:jvm中每个对象都会有一个监视器Monitor,监视器和对象一起创建、销毁。监视器相当于一个用来监视这些线程进入的特殊房间,其义务是保证(同一时间)只有一个线程可以访问被保护的临界区代码块。每一个锁都对应一个monitor对象。同步实现是监视器所保护的临界区代码是互斥地执行的。一个监视器是一个运行许可,任一线程进入临界区代码都需要获得这个许可,离开时把许可归还。协作:监视器提供Signal机制,允许正持有许可的线程暂时放弃许可进入阻塞等待状态,等待其他线程发送Signal去唤醒;其他拥有许可的线程可以发送Signal,唤醒正在阻塞等待的线程,让它可以重新获得许可并启动执行。
锁升级:
锁的4种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。 为了减少重量级锁带来的性能开销,尽可能的在无锁状态下解决线程并发问 题,其中偏向锁和轻量级锁的底层实现是基于自旋锁。
AQS
作用:实现高效锁,Lock类锁基于抽象同步器实现的。
原理:AQS (AbstractQueuedSynchronizer)使用一个 int 成员变量State来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒 时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CAS
作用:Atomic类的实现都依赖于CAS(compare and swap) 算法。
原理:JDK提供的CAS机制,在汇编层级会禁止变量两侧的指令优化,然后使用cmpxchg指令比较并更新变量值(原子性);CAS是JDK提供的非阻塞原子性操纵,它通过硬件保证了比较-更新的原子性,更加可靠。它的底层是一条原子指令(cmpxchg),不会造成数据不一致问题,Unsafe类提供的CAS方法(compareAndSwapXXX)地城实现都是CPU指令cmpxchg。执行cmpxchg的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个人线程会对总线枷锁成功,加锁成功后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,这里的排他时间要短很多,所以在多线程的情况下性能会更好。
简言之:CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性。实现方式是基于硬件平台的汇编指令,在X86上使用的hi汇编指令cmpxchg,核心思想就是 比较想要更新的值V和预期值E,相等才会将V的值设置为新值N,如果不相等则自旋。
缺点:
1、循环时间长开销很大,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPu带来很大的开销。
2、ABA问题,解决方法是版本号时间戳原子引用。
自旋锁
试获取锁的线程不会立即阻塞而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取
Lock 接口
作用:提供了锁的操作定义,子类有ReentrantLock
接口函数:
void lock()//获取锁,如果锁不可用,则出于线程调度的目的,当前线程将被禁用,并且在获取锁之前处于休眠状态
void unlock();//如果锁可用立即返回true,如果锁不可用立即返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException. /*如果锁可用,则此方法立即返回true。 如果该锁不可用,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况之一为止:①当前线程获取到该锁;②当前线程被其他线程中断,并且支持中断获取锁;③经过指定的等待时间如果获得了锁,则返回true,没获取到锁返回false*/
void unlock()://释放锁。释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生
ReentrantLock
原理:ReentrantLock的锁功能主要是通过继承了AbstractQueuedSynchronizer的内部类Sync来实现的,其lock()获取锁。ReentrantLock的lock()方法会调用其内部成员变量sync的lock()方法---> sync的非公平锁NonfairSync或公平锁FairSync实现了父类AbstractQueuedSynchronizer的lock()方法,其会调用acquire()方法;------>通过tryAcquire()方法试图获取锁,获取到直接返回结果,否则通过嵌套调用acquireQueued()、addWaiter()方法将请求获取锁的线程加入等待队列,如果成功的话,将当前请求线程阻塞。
公平锁的实现原理:ReentrantLock 类锁基于AQS,该同步器内的FIFO的队列CLH管理线程,公平锁按照线程入队的顺序执行。非公平锁是线程执行两次CAS去获取锁,如果没有活到加入到CLH,等待唤醒。
ReentrantReadWriteLock
作用:读写锁独立,读锁是乐观锁,写锁是悲观互斥锁。读锁/写锁 已被其它线程占用,那么新来的线程将无法获取写锁。写锁可重入。提高读锁效率。
原理:ReentrantReadWriteLock 并没有像ReentrantLock一样直接实现Lock 接口,而是内部分别持有ReadLock、WriteLock类型的成员变量。
StampedLock
作用:在JDK1.8之后提供,读写锁虽然分离了读和写的功能,使得读与读之间可以完全并发,但是读和写之间依然是冲突的,读锁会完全阻塞写锁,它使用的依然是悲观的锁策略.**如果有大量的读线程,他也有可能引起写线程的饥饿。
锁模式:写锁、悲观读锁和乐观读。StampedLock 里的写锁和悲观读锁加锁成功之后,都会返回一个 stamp;然后解锁的时候,需要传入这个 stamp。ReadWriteLock 支持多个线程同时读,但是当多个线程同时读的时候,所有的写操作会被阻塞;而 StampedLock 提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都被阻塞。
注意事项:不支持重入;悲观读锁、写锁都不支持条件变量;定不要调用中断操作
如果线程阻塞在 StampedLock 的 readLock() 或者writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。
CountDownLatch
作用: CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完 成,这里就传入N。这里所说的N个 点,可以是N个线程,也可以是1个线程里的N个执行步骤。适用于多个任务完成后再执行之后的工作场景。不可重复使用。
原理:创建一个Sync对象,Sync是 AQS同步队列的实现,并将计数器的值赋给了AQS的state,countDown()方法会对计数器进行减1的操作,当计数器值为0时,将会唤醒在阻塞队列中等待的所有线程。其内部调用了Sync的releaseShared(1)方法(这个state的值在有新的的任务执行时-1,直到为0就阻塞,将新任务放到FIFO的CLH队列里等待唤醒)。而await()当计数器的值不为0时,该方法会将当前线程加入到阻塞队列中,并把当前线程挂起。
CyclicBerrier
作用:CyclicBarrier是可循环使用(Cyclic)的屏障(Barrier),通过它可以实现让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,所有被屏障拦截的线程才会继续执行。CyclicBarrier 可重置后再次使用。
原理:CyclicBarrier类中的ReentrantLock对象和Condition对象用于控制线程,parties变量表示倒计数器的最大值,count变量表示倒计数器当前值,而Runnable对象即为触发点任务。await方法。它会调用dowait方法,所以我们主要看dowait方法。首先为了线程安全,通过lock.lock()进行加锁,然后判断当前线程是否被中断,如果被中断则往上抛InterruptedException异常。线程调用await方法会让倒计数器减一,所以接下去会将当前倒计数器的当前值减一。如果倒计数器当前值为0则需要执行一个Runnable对象,它就是前面构造函数传入的触发点任务,然后调用nextGeneration方法进入下一轮。而如果倒计数器当前值不为0的话,则调用Condition对象的await方法进入等待状态,当然如果设置了超时的话则使用awaitNanos方法,中间如果发生中断异常则通过Thread.currentThread().interrupt()设置当前线程的中断标识。此外,如果等待时间超过指定时间则抛TimeoutException异常。最后调用lock.unlock()释放锁。
Semaphore
作用:Semaphore通过使用计数器来控制对共享资源的访问。 如果计数器大于0
,则允许访问。 如果为0,则拒绝访问。 计数器所计数的是允许访问共享资源的许可。 因此,要访问资源,必须从信号量中授予线程许可。适用于指定个数的资源,资源有限,大于资源个数的线程,安排工作。一般用于流量的控制,特别是公共资源有限的应用场景。例如数据库的连接,假设数据库的连接数上线为10个,多个线程并发操作数据库可以使用Semaphore来控制并发操作数据库的线程个数最多为10个。
原理:Semaphore还分别实现了公平模式FairSync和非公平模式NonfairSync两个内部类。semaphore.acquire() 方法用于抢占资源,acquire() 调用最终会进入Sync.nonfairTryAcquireShared(),并且对state的值原子性减1,state是 AbstractQueuedSynchronizer 维护的一个状态值,也可以说是计数器,可以说AbstractQueuedSynchronizer同步器的实现很大程度都依靠 state 来实现;前3个抢占到资源的线程每个都对 state 的值减1;当达到最大任务数后,并没有可用的资源使用,此时 state 的值为0,进入doAcquireSharedInterruptibly() 方法,对于没有抢占到资源的线程会维护成一个链表的形式,最后调用 LockSupport.park(this) 挂起该线程。