如何做网站规范,有账号和密码怎么进公司网站后台,网站开发精灵,网站建设策划书色彩设计方案并发编程挑战
上下文切换
cpu通过给每个线程分配cpu时间片实现多线程执行#xff0c;时间片是cpu分配给各个线程的时间#xff0c;cpu通过不断切换线程执行。线程有创建和上下文切换的开销。减少上下文切换的方方法 – 无锁并发编程#xff0c;eg#xff1a;将数据的id按…并发编程挑战
上下文切换
cpu通过给每个线程分配cpu时间片实现多线程执行时间片是cpu分配给各个线程的时间cpu通过不断切换线程执行。线程有创建和上下文切换的开销。减少上下文切换的方方法 – 无锁并发编程eg将数据的id按照hash算法取模分段不同线程处理不同段的数据 – cas算法java的atomic包使用cas算法来更新数据不需要加锁 – 使用最少的线程 – 协程在单线程里实现多任务的调度并在单线程里维持了多个任务间的切换
死锁
避免死锁的方法
避免一个线程同时获取多个锁避免一个线程在锁内同时占用多个资源尽量保证每个锁只占用一个资源尝试使用定时锁使用lock.tryLocktimeout来替代使用内部锁机制对于数据库锁加锁和解锁必须在一个数据库连接里否则出现解锁失败的情况
资源限制的挑战
资源限制
在进行并发编程时程序的执行速度受限于计算机硬件资源或软件资源
引起的问题
串行代码变成并发执行时如果受限于资源仍然串行执行不仅不会快反而会更慢增加了上下文切换和资源调度的时间。 例如使用多线程在办公网并发地下载和处理数据时导致cpu利用率达到100%几个小时都不能运行完成任务后来修改成单线程一个小时就执行完成了。
解决方法
硬件资源考虑集群并行执行程序软件资源使用资源池将资源复用
资源限制情况下进行并发编程
将不同的资源限制调整程序的并发度
并发机制的底层实现原理
java代码编译后会编程java字节码字节码被类加载器加载到jvm里jvm执行字节码最终转为汇编指令在cpu上执行
volatile应用
轻量级保证共享变量的可见性 可见性当一个线程修改一个共享变量时另外一个线程能读到这个修改的值。
实现原理
如果一个字段被声明成了volatilejava线程内存模型确保所有线程看到这个变量的值是一致的。
cpu术语
内存屏障实现对内存操作的顺序限制缓冲行缓存中可以分配的最小存储单位原子操作不可中断的一个或一系列操作缓存行填充当处理器识别到从内存中读取操作数是可缓存的处理器读取整个缓存行到适当的缓存缓存命中写命中写缺失
Java代码
instance new Singleton() ;//instance是volatile变量转成汇编代码
0x01a3de1d:movb $0x0,0x1104800(%esi);0x01a3de24:lock add1 $0x0,(%esp);Lock前缀的指令在多核处理器引发两件事情
将当前处理器缓存行的数据写回到系统内存写回内存的操作会使其他cpu里缓存了该内存地址的数据无效
过程
处理器先将系统内存的数据读到内存缓存后再操作对声明了volatile的变量进行写操作jvm会向处理器发送一条lock前缀指令将这个变量所在缓存行的数据协会到系统内存多处理器下实现缓存一致性每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了处理器发现自己缓存行对应的内存地址被修改就会将当前处理器缓存行设置成无效状态当对数据修改操作会重新从内存中把数据读到处理器缓存中。
volatile两条实现原则
缓存锁定Lock前缀指令会引起处理器缓存会写到内存Lock#信号一般不锁总线锁内存。缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。一个处理器的缓存回写到内存会导致其他处理器的缓存无效处理器使用嗅探技术保证他的内存缓存系统内存和其他处理器的缓存数据在总线上保持一致。
使用优化
jdk7 并发包中新增了一个队列集合类Linked-TransferQueue在使用volatile时用一种追加字节的方式来优化队列出队和入队的性能。 LinkedTransferQueue结构 内部类型定义队列的头节点和尾节点内部类PaddedAtomicReference 追加字节能优化队列出队和入队性能内部类PaddedAtomicReference相对于父类AtomicReference多了将共享变量追加到64字节15个变量父类valule变量 为什么提高并发编程效率有些处理器的l1l2或l3缓存的高速缓存行使64字节宽。追加到64字节填满高速缓冲区的缓存行避免了头结点和为节点加载到同一个缓存行使头尾节点在修改时不会互相锁定。 不能使用追加64场景 缓存行非64字节的处理器egP6系列和奔腾处理器是32字节共享变量不会被频繁地写 synchronized
synchronized实现同步的基础Java中的每个对象都可以作为锁
普通同步方法锁是当前实例对象静态同步方法锁是当前类的class对象同步方法块锁是synchronized括号里配置的对象
Monitor指令
monitorenter指令是在编译后插入到同步代码块的开始位置monitorexit是插入到方法结束处和异常处任何对象都有一个monitor与之关联当且一个monitor被持有后处于锁定状态。线程执行到monitorenter指令时将会尝试获取对象对应的monitor所有权尝试获取对象的锁。
java对象头
synchronized用的锁是存在java对象头里的如果对象是数组类型虚拟机用3个字宽存储对象头如果对象是非数组类型用2字宽存储对象头。 Mark World存储对象的hashCode分代年龄和锁标记位 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oshe1Pci-1692516244748)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230820120519714.png)] Class Metadata Address存储到对象类型数据的指针 Array length数组的长度如果当前对象是数组
锁的升级与对比
锁状态无锁状态偏向锁状态轻量级锁状态重量级锁状态锁可以升级但不能降级目的是为了提高获得锁和释放锁的效率
偏向锁
多数情况下锁不仅不存在多线程竞争而且总是由同一个线程多次获得。
**偏向锁撤销**等到竞争出现才释放锁的机制当其他线程尝试竞争偏向锁时持有偏向锁的线程才会释放锁。
无锁thread1 访问同步块检查对象头中是否存储了thread1没有cas方法替换mark word偏向锁成功。将对象头mark word中的线程id指向自己执行同步体偏向锁同时 thread2访问同步块检查对象头是否存储了thread2没有cas替换mark word不成功撤销偏向锁偏向锁撤销thread1暂停线程解锁将线程id设为空恢复线程
关闭偏向锁
java6和java7中默认启用但在应用程序启动几秒之后才激活
关闭延迟-XXBiasedLockingStartupDelay0
关闭偏向锁-XX-UseBiasedLockingfalse
轻量级锁
加锁
线程执行同步块之前jvm先在当前线程的栈帧中创建用于存储锁记录的空间将对象头中的mark word复制到锁记录中线程尝试使用cas将对象头中的mark word替换为指向锁记录的指针 成功当前线程获取锁失败其他线程竞争锁当前线程尝试自旋获取锁
解锁
cas将displaced mark word 替换回对象头 成功表示没有竞争发生失败当前锁存在竞争膨胀成重量级锁
轻量级锁膨胀流程
无锁thread1 和thread2 访问同步块分配空间并复制mark word 到栈。轻量级锁thread1 和thread2 进行cas修改mark word thread1 成功将mark word替换为轻量级锁执行同步体thread2 失败因为thread1获取了锁thread3自选获取锁 升级为重量级锁 thread2 锁膨胀修改为重量级锁线程阻塞thread1 cas替换mark word 失败因为thread2在争夺锁thread1释放锁并唤醒等待的线程。thread2线程被唤醒重新争夺锁访问同步块
当锁处于重量级其他线程试图获取锁时会被阻塞当持有锁的线程释放锁后会唤醒这些线程被唤醒的线程会进行新一轮的夺锁之争。
优缺点对比
偏向锁 优点加锁解锁不需额外消耗缺点如果线程间存在锁竞争会带来额外撤销的消耗场景适用于只有一个线程访问同步块的场景 轻量级锁 优点竞争线程不会阻塞提高响应速度缺点如果始终得不到锁竞争的线程使用自旋会消耗cpu场景追求响应时间同步块执行速度非常快 重量级锁 优点线程竞争不使用自旋转不消化cpu缺点线程阻塞响应时间缓慢场景追求吞吐量同步执行速度较长
原子操作的实现原理
术语
缓存行缓存的最小操作单位比较并替换cas比较旧值是否发生变化交换成新值否则不交换cpu流水线一条x86指令分成56步后有56个不同电路单元分别执行实现在一个cpu时钟周期完成一条指令。内存顺序冲突假共享引起假共享指多个cpu同时修改同一个缓存行的不同部分引起其中一个cpu的操作无效当出现这个内存顺序冲突时cpu需清空流水线
处理器如何实现原子操作
基于对缓存加锁或总线加锁方式实现多处理器之间的原子操作
使用总线锁保证原子性
使用处理器提供的Lock#信号当一个处理器在总线上输出此信号时其他处理器的请求将被阻塞住那么该处理器可以独占共享内存。
总线锁把cpu和内存之间的通信锁住了其他处理器不能操作其他内存地址的数据总线锁定的开销比较大。
使用缓存锁保证原子性
频繁使用的内存会缓存在处理器L1L2和L3高速缓存里。缓存锁定内存区域如果被缓存在处理器的缓存行中并且在Lock操作锁定期间被锁定那么当他执行锁操作会写到内存时处理器不在总线上声言LOCK#信号而是修改内部的内存地址并允许它的缓存一致性机制来保证操作的原子性。缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据当其他处理器回写已被锁定的缓存行数据时会使缓存行无效。不能使用缓存锁定的情况 当操作的数据不能被缓存在处理器内部或操作的数据跨多个缓存行时则处理器会调用总线锁定有些处理器不支持缓存锁定对于Intel486和Pentium处理器就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。
java实现原子操作
使用cas实现原子操作
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;public class Counter {private AtomicInteger atomicInteger new AtomicInteger(0);private int i 0;public static void main(String[] args) {final Counter cas new Counter();ListThread ts new ArrayListThread(600);long start System.currentTimeMillis();for (int j 0; j 100; j) {Thread thread new Thread(new Runnable() {public void run() {for (int i 0; i 10000; i) {cas.count();cas.safeCount();}}});ts.add(thread);}for (Thread t : ts) {t.start();}//等待所有线程执行完成for (Thread t : ts) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(cas.i);System.out.println(cas.atomicInteger.get());System.out.println(System.currentTimeMillis()-start);}private void safeCount(){for (;;){int i atomicInteger.get();boolean b atomicInteger.compareAndSet(i, i);if(b){break;}}}private void count(){i;}
}执行结果
java1.5jdk提供了一些类支持原子操作AtomicBooleanAtomicInteger和AtomicLong
cas三大问题
java并发包中有些并发框架使用了自旋cas方式实现原子操作。
ABA 问题旧值由A变成B再变成A最终值没变但实际发生了变化解决变量前增加版本号Atomic包中AtomicStampedReference解决ABA问题 先检查当前引用是否等于预期引用检查当前标志是否等于预期标志全部相等更新值 循环时间长开销大自旋cas长时间不成功cpu带来非常大的执行开销 jvm支持处理器提供的pause指令效率提升作用 可以延迟流水线执行指令是cpu不会消耗过多的执行资源延迟时间取决于具体实现版本避免在退出循环时因内存顺序冲突引起cpu流水线被清空提高执行效率 只能保证一个共享变量原子操作 对多个共享变量操作时不能保证原子性可以用锁或者多个共享变量合成一个共享变量。AtomicReference类保证引用对象之间的原子性可以把多个变量放到一个对象里进行cas操作
使用锁机制实现原子操作
锁机制保证了只有获得锁的线程能操作锁定的内存区域偏向锁轻量锁互斥锁除了偏向锁jvm实现锁的方式都用了循环cas 当一个线程想进入同步块时使用循环cas的方式来获取锁当退出同步块的时候使用循环cas释放锁。
参考Java并发编程的艺术