java多线程
2021-05-12 11:28:00 12 举报
AI智能生成
登录查看完整内容
java多线程
作者其他创作
大纲/内容
Lock
带有高速缓存的CPU执行计算的流程1. 程序以及数据被加载到主内存2. 指令和数据被加载到CPU的高速缓存3. CPU执行指令,把结果写到高速缓存4. 高速缓存中的数据写回主内存CPU缓存一致性协议(MESI):缓存行(Cache line):缓存存储数据的单元。四种状态:修改-Modified、独享-Exclusive、共享-Shared、无效-Invalid实现方式:1. 总线锁;2. 缓存锁;锁升级:无锁 -> 偏向锁 -> 自旋锁(轻量级锁,自旋10次锁升级) -> 重量级锁
CAS
概述:CAS(compare and swap)操作属于乐观锁(又称自旋锁),每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。span style=\"font-size: inherit;\
volatile
保证线程可见性:1. 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。2. 不能保证原子性。可使用Lock、synchronized、java.util.concurrent.atomic 包下的类实现。禁止指令重排序内存屏障保证了位于内存屏障之前的所有操作先完成于内存屏障后面的所有操作。所以指令无法越过内存屏障,也就是无法重排序。原语指令:loadfence、storefence。
ThreadLocal
T get()Thread.currentThread.ThreadLocalMap.get(ThreadLocal); // 获取当前线程的map中的value
实例使用:spring的声明式事务实现,把数据库的connection对象放到TheadLocal中,同一个线程追溯的多个方法就能获取到同一个数据库的连接对象,进而利用数据库的事务实现数据的原子性。
synchronized
概述:早期1.6之前,synchronized是重量级锁,申请加锁阻塞或唤醒一条线程均需要操作系统帮忙完成,这就需要调用从用户态转换到内核态(0x80中断-system_call系统调用),状态转换需要耗费很多cpu时间。所以synchronized是一个重量级操作。1.6中加入了针对锁的优化。
对象的内存部局(可参考 JVM内存部局篇章)MarkWord(8字节)、ClassPointer(默认压缩4字节)、instance data、padding(填充对齐,使对象占用空间大小为8的整数倍)。前两个组成可称为对象头。MarkWord中记录了对象的GC信息、hashcode、锁信息。锁信息存储在MarkWord中64位的最低两位。无锁态和偏向锁最低两位相同,还需要第三位加以区分。锁类型及锁升级无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,目的是为了提高获得锁和释放锁的效率。1. 偏向锁:大多数情况下锁不存在多线程竞争,总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁,减少不必要的CAS操作。获取过程: 当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。(1)访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。(2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。(3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。(4)如果CAS获取偏向锁失败,则表示有竞争(CAS获取偏向锁失败说明至少有过其他线程曾经获得过偏向锁,因为线程不会主动去释放偏向锁)。当到达全局安全点(safepoint)时,会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着(因为可能持有偏向锁的线程已经执行完毕,但是该线程并不会主动去释放偏向锁),如果线程不处于活动状态,则将对象头设置成无锁状态(标志位为“01”),然后重新偏向新的线程;如果线程仍然活着,撤销偏向锁后升级到轻量级锁状态(标志位为“00”),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。(5)执行同步代码。释放过程:如上步骤(4)。偏向锁使用了一种等到竞争出现才释放偏向锁的机制:偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。关闭偏向锁:偏向锁在Java 6和Java 7里是默认启用的。由于偏向锁是为了在只有一个线程执行同步块时提高性能,如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。2. 轻量级锁这种锁实现的背后基于这样一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态(即单线程执行环境),在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁,取而代之的是在monitorenter和monitorexit中只需要依靠一条CAS原子指令就可以完成锁的获取及释放。当存在锁竞争的情况下,执行CAS指令失败的线程将调用操作系统互斥锁进入到阻塞状态,当锁被释放的时候被唤醒。加锁过程:(1)在代码进入同步块的时候,如果同步对象锁状态为无锁状态,虚拟机首先将锁对象的对象头MarkWord复制一份到当前线程的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为当前线程的锁记录的地址。(2)拷贝对象头中的Mark Word复制到锁记录中。(3)拷贝成功后,虚拟机将使用CAS操作尝试将锁对象的Mark Word更新为指向Lock Record的指针地址,并将Lock Record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。(4)如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。(5)如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,若当前只有一个等待线程,则可通过自旋稍微等待一下,可能另一个线程很快就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。解锁过程:(1)通过CAS操作尝试把锁对象的Mark Word修改为线程中复制的Displaced Mark Word对象指针地址。(2)如果替换成功,整个同步过程就完成了。(3)如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。3. 重量级锁如上轻量级锁的加锁过程步骤(5),轻量级锁所适应的场景是线程近乎交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。Mark Word的锁标记位更新为10,Mark Word指向互斥量(重量级锁)Synchronized的重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。
AQS
【参考链接】
概述:抽象队列式同步器,定义一套多线程访问共享资源的同步器框架,依赖其的同步类有 ReentrantLock、Semaphore、CountDownLatch 等
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)volatile是核心关键词,且通过 compareAndSetState (CAS) 原子性操作state的值。其中还使用到了模版方法设计模式,如 子类NonfairSync重写了父类Sync的父类AbstractQueuedSynchronizer的tryAcquire(int arg)方法
释放锁重要方法:span style=\"font-size: inherit;\
Thread
static native void sleep(Long millis)
static native void yield()
void join()
多线程与高并发
<Lock>ReentrantLock
void lock()
void unlock()
boolean tryLock()
void lockInterruptibly()
Condition newCondition()
condition.await()
condition.signal()
LockSupport
static void park()
static void unpark(Thread)
CountDownLatch
CountDownLatch(int)
void countDown()
void await()
CyclicBarrier
int await()
Phaser
<ReadWriteLock>ReentrantReadWriteLock
class ReadLock implements Lock
class WriteLock implements Lock
ReentrantReadWriteLock.ReadLock readLock()
ReentrantReadWriteLock.WriteLock writeLock()
Semaphore
Semaphore(int)
void acquire()
void release()
Exchanger
V exchange(V x)
ReentrantLock流程图
0 条评论
回复 删除
下一页