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中的一些优化,效率上比较低,因此必要时使用。

posted on 2022-03-10 15:18  翻滚的咸鱼  阅读(174)  评论(0编辑  收藏  举报