实现原理:通过CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,时间片非常短,CPU通过不停的切换线程执行,让我们感觉多个线程是同时执行的。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片会切换到下一个任务,并保存上一个任务的状态,下次切换到这个任务时就会加载这个状态,这就是一次上下文切换。
上下文切换会影响多线程执行速度的。
上下文切换和线程创建在某些时候甚至会导致并发比串行慢。
减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程等。
无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些
办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
CAS算法:CAS算法通过比较内存中的值与预期值是否相等来判断内存中的值是否被其他线程修改过,并在相等的情况下将新值写入内存。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
当两个线程互相等待对方获得锁的时候就会造成死锁。
一般来说,是因为没有正常释放锁造成的。
避免死锁的方法:
避免一个线程同时获取多个锁,避免一个线程在锁内同时占用多个资源,使用定时锁。
volatile保证了共享变量的“可见性”。volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。
通过Lock前缀的指令,将当前处理器缓存行的数据写回到系统内存。使在其他CPU里缓存了该内存地址的数据无效。
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了
Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
·对于普通同步方法,锁是当前实例对象。
·对于静态同步方法,锁是当前类的Class对象。
·对于同步方法块,锁是Synchonized括号里配置的对象。
锁一共有4种状态,从低到高是无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。
锁可以升级但不能降级。
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。加锁和解锁不会有额外的性能消耗,适用于只有一个线程访问同步块的场景,如果线程之间存在竞争会导致额外的锁撤销的消耗。
竞争的线程不会阻塞,会通过自旋来尝试获取锁。缺点是一直没有得到锁的线程持续自旋会导致CPU消耗,适用于追求响应速度的场景
线程竞争通过阻塞实现,响应速度慢,适用于追求吞吐量的环境,同步块执行速度慢。
处理器一般使用总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。
当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可
以独占共享内存。
当一个处理器要修改一个经常被其他处理器读取的内存地址时,它可以向所有其他处理器发送一个请求,在请求期间,其他处理器会将与该地址相关的缓存行设置为无效状态,即将其从缓存中移除。这就是缓存锁定的过程,也被称为缓存锁定操作。在这个过程中,其他处理器无法读取或修改与该地址相关的数据,直到缓存锁定操作完成。
自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止。
CAS实现原子操作的三大问题
ABA问题,循环时间长开销大,以及只能保证一个共享变量的原子操作。
1.ABA问题解决方案:通过版本号来解决,在变量前面追加上版本号,每次变量更新的时候把版本号加1。
2.循环时间开销问题:延迟自旋的速度来减少CPU的消耗。
3.只能保证一个变量原子性,可以通过多个共享变量合并成一个变量来操作。
JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。