CAS(Compare and Set)
无锁算法,不使用锁的情况下实现多线程之间的变量同步,拿变量的原值和内存中的值进行比较,如果相同,则原值没有被修改过,那么就将原值修改为新值,失败的线程不会挂起,继续循环;
Java 中的 AtomicInteger 类就用了CAS操作。
AtomicInteger atomicInteger = new AtomicInteger(1); //先对比,后设置,如果等于1,更新值为2 Boolean b =atomicInteger.compareAndSet(1, 2);
比起锁来性能提升了许多,但是因为频繁循环,会照成系统开销增大。
ABA问题:如果值一开始是A,后来被改成了B又被改回了A,CAS会认为该变量没被修改过,不过 Java 也提供了带时间戳的类来解决这个问题。
AQS(AbstractQueuedSynchronizer)
抽象队列同步器,支持 独占(exclusive ) 与 共享(shared) 两种模式,维护一个整形的 volatile 变量 state 和队列来实现同步,通过 CAS 来更新state值(线程个数一致),AQS 也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock
;
直接尝试占有,先判断state是否为0(可获取),然后将0累计加1,当前线程设置为锁持有(lock())线程,并返回成功;如果失败则依次排队,释放锁(unlock())state-1;
java很多同步框架都依赖于AQS,比如 CountDownLatch,ReentrantLock,Semaphore。
同步器基于模板方法模式,需要自定义模版继承 AbstractQueuedSynchronizer ,重写方法(state 的获取和释放),Semaphore
(信号量)可以指定多个线程同时访问某个资源。
Semaphore
基于 AQS 实现,指定线程数量,当执行任务的线程数超出,多余的线程将会被放入阻塞队列。
CountDownLatch
基于 AQS 实现,是一次性的,计数器的值只能在构造方法中初始化一次。
CyclicBarrier
指定线程数量,线程到达一个屏障会被阻塞(变量标识计数),直到指定的最后一个线程也到达屏障时,屏障才会打开,然后线程继续运行,还可以指定到达屏障时,优先执行某些代码块。
ReentrantLock(公平锁/非公平锁)
依赖于AQS同步框架实现,可以指定构造函数的boolean类型来得到公平锁或非公平锁。
公平锁:先进先出,多个线程按照队列的规则依次排队。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列。
对比公平锁更有机会抢占锁,性能高于公平锁,因为线程运行之间存在着延迟,非公平锁能更充分的利用时间碎片。
synchronized(同步锁)
表示只能有一个线程访问该对象,jdk6之前是操作系统的 mutex lock(互斥锁)实现,但是由于 mutex 性能上的问题,在线程挂起时会从用户状态切换到内核状态来;
切换代价消耗大,后面加入了CAS跟许多优化机制,所以性能跟 ReentrantLock 基本相等。
静态方法类需要注意,如果new不同对象调用同一个静态锁方法,此时对象间会共用一把锁,使该线程下方法里的代码块同步进行。
mutex
Linux 系统中称 mutex 为互斥体,是一种存取共享内存资源的机制,在数据被进程持有时不允许其它进程从共享内存中读取信息、也不允许同时读取操作。
可重入锁/不可重入锁
可重入锁:当线程获取某个锁后,还可以继续获取它,可以递归调用,而不会发生死锁,ReentrantLock 跟 synchronized 都是可重入锁。
不可重入锁:与可重入相反,获取锁后不能重复获取,否则会自己锁死自己。
独享锁/共享锁
独享锁:该锁每一次只能被一个线程所持有,比如 synchronized。
共享锁:该锁可被多个线程共有。
互斥锁/读写锁
互斥锁:mutex lock,如果资源已经被加锁,其它线程进入阻塞,直到当前进程解锁,synchronized 为互斥锁。
读写锁:读写锁既是互斥锁,又是共享锁,读模式是共享,写是互斥,读读不互斥,读写互斥,写写互斥。
乐观锁/悲观锁
乐观锁:假设都是无锁状态。
悲观锁:假设都是上锁状态。
分段锁
是一种锁的设计,并不是具体的一种锁,ConcurrentHashMap 前期就是通过分段锁来实现高效的并发操作。
每一把锁都用于数据段,当多线程访问不同数据段的数据时,从而降低锁竞争,提高并发访问效率。
自旋锁
线程在锁被占用时(不是阻塞),会不断循环去尝试,直到获取锁,使得线程会保持活跃,减少了线程切换的开销,但加重了等待时间,它是为实现保护共享资源而提出一种锁机制。
偏向锁/轻量级锁/重量级锁
不属于java锁,而是 Jvm 为了提高锁的获取与释放效率而做的优化。
偏向锁:某线程一直访问 synchronized 锁代码,那么该线程会自动获取锁,降低获取锁的代价。
轻量级锁:在偏向锁的基础上,另一个线程加入访问,偏向锁会自动升级为轻量级锁,其他线程就可以通过自旋的形式尝试获取锁而不被阻塞,以此提高性能。
重量级锁:在轻量级锁的基础上,另一个线程加入访问,处于自旋,当自旋一定次数还没有获取到锁,就会进入阻塞,该锁变为重量级锁,重量级锁会让其他申请的线程进入阻塞。
volatile
高速缓存:多核多线程操作(单核不会),会对数据进行缓存,修改后在设置回内存中,这样会导致写入数据时(会让其他线程缓存中的值过期,这样就必须重新获取最新的值)同步出错,volatile可以解决可见性问题,线程修改完值后其它线程立即可见。
指令重排:CPU在执行代码时(编译器也会),不一定是我们编写的顺序去执行,为了提高效率,会对这些先后顺序无关紧要的代码(数据无依赖关系)进行重新排序,导致有的初始化完成但是还没赋值,下一个线程操作时就会异常,volatile会告诉CPU禁止对它重排。
使用volatile会屏蔽掉JVM中的一些优化,效率上比较低,因此必要时使用。