【JUC】21-JUC总结

01-进程、线程和管程

 管程:Monitor监视器,即锁。
 Monitor是一种同步机制,用来保证同一时间只会有一个线程可以访问被保护的数据或代码
JVM的同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每一个对象实例都会有一个Monitor对象。

02-FutureTask

 Future用于处理异步同时任务。FutureTask继承了RunnableFuture,可以使用Runnable和Callable进行初始化,下面为使用FutureTask的一个例子。

优点:future+ThreadPool异步多线程任务配合,能显著提高程序的执行效率。
缺点:

  • get()阻塞。方法容易阻塞主线程。一般将Future.get放在最后。
  • isDone()轮询。Future对于结果的获取不友好,只能通过阻塞或轮询的方式得到任务的结果。

03-CompletableFuture使用

 CompletableFuture可以进行回调通知、创建异步任务、多个任务前后依赖可以组合处理、对计算速度选最快。
 CompletableFuture提供了一种类似于观察者模式的通知方式,可以在任务完成后通知监听方。
 CompletableFuture实例化用CompletableFuture.runAsync()和CompletableFuture.supplyAsync()。

04-synchronized关键字

 synchronized锁是实例锁,定义不同的实例,有不同的锁。对于同一个对象,那个方法先拿到锁,哪个就先执行
 static synchronized锁的是Object模板类,和synchronized的实例锁不是同一把锁,被他们修饰的方法可以同时执行。

05-公平锁和非公平锁

  • 公平锁:多个线程按照申请锁的顺序来获取锁。
  • 非公平锁:默认为非公平锁,容易造成优先级翻转和饥饿的状态。充分利用CPU空闲时间,减少线程切换开销。
    &esmp;ReentrantLock lock = new ReentrantLock(true)设为公平锁。

06-可重入锁

 可重入锁:又称递归锁。在外层使用锁后,内层仍然可以使用,并不发生死锁,这样的锁就叫可重入锁。synchronized默认是一个可重入锁。

07-死锁

 死锁指的是两个或以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。
 jstack pid用来排查是否存在死锁。

08-中断机制之中断协商机制

 一个线程应该自己主动去中断。所以Thread.stop, Thread.suspend, Thread.resume都已经被废弃。其他线程设置中断标识位为true,本线程会自行决定是否需要结束本线程。

  • void interrupt()设置线程的中断状态为true,发起一个协商而不会立刻停止线程。
  • static boolean interrupted()判断线程是否被中断并清除当前中断状态:1. 返回当前线程的中断状态,测试当前线程是否已被中断; 2. 将当前线程的中断状态清零并重新设为false,清除线程的中断状态。
  • boolean isInterrupted()测试此线程是否已被中断。
    中断实现:
  • volatile实现线程中断
  • AtomicBoolean实现中断协商
  • Thread API实现协商中断

09-线程等待与唤醒

  • Object wait和notify实现等待与唤醒,使用synchronized将Object对象锁起来。
  • Lock.newCondition condition.await和condition.signal实现等待与唤醒。
  • LockSupport park和unpark实现等待与唤醒。unpark只能产生一个permit且可以提前产生。

10-Java内存模型JMM

JMM三大特性:

  • 原子性:互斥,同时成功或失败。
  • 有序性:指令重排序后有序。
  • 可见性:当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更,JMM规定了所有变量都存储在主内存中。
     线程拷贝共享变量的副本,进行操作,然后再写入主内存。
     本线程的共享变量是私有的。要进行不同线程之间的数据读取,需要通过主内存进行中转。

多线程先行发生原则之happens-before:
 在JMM中,如果一个操作执行的结果需要对另一个操作可见或者代码重排序,那么这两个操作之间必须存在happens-before(先行发生原则)。逻辑上的先后关系。
总原则:
 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

11-volatile关键字

volatile关键字修饰变量特点:

  • 不支持原子性:不能保证同时时间只有一个线程操作数据。
  • 可见性:对一个被volatile关键字修饰的变量,1. 写操作,这个变量的最新值会立即刷新回到主内存中。 2. 读操作,总是能够读取到这个变量的最新值,也就是这个变量最后被修改的值。 3. 当某个线程收到通知,去读取volatile修饰的变量的值的时候,线程私有工作内存的数据失效,需要重新回到主内存中读取最新的数据。
  • 有序性:有时需要禁止重排序。

12-CAS

compare and swap,比较并交换。
包含三个操作数:内存位置、预期原值及更新值。
执行CAS操作的时候,将内存位置的值与预期原值比较:

  • 匹配,更新为新值。
  • 不匹配,不进行操作,多个线程同时执行CAS操作只有一个会成功。

CAS是JDK提供的非阻塞原子性操作,通过硬件保证比较-更新的原子性。底层实现为CPU指令cmpxchg,效率更高。通过unsafe类实现。
CAS缺点:

  • 循环时间长,开销大
  • ABA问题

解决方案:版本号,戳记流水。
atomicStampedReference.compareAndSet(zs, ls, atomicStampedReference.getStamp(), 2);

13-原子类

 CountDownLatch实现线程等待。
 AtomicMarkableReference流水号为Boolean类型,只能改为true和false。
 AtomicIntegerUpdater用于实现数据的原子性,但程序运行速度下降会很少。可以对对象的某一属性进行加锁,而不需要锁整个对象,但对象的这个属性需要被volatile修饰。
 AtomicReferenceFieldUpdater可以用于更多的类型。
 LongAddr和LongAccumulator实现高效的自增操作。

14-LongAddr源码分析

  LongAddr基本思想是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突概率就很小。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
 sum执行时,并没有限制对base和cells的更新。所以LongAddr不是强一致性的,它是最终一致性。

15-ThreadLocal线程局部变量

 ThreadLocal提供线程局部变量。每个线程在访问ThreadLocal实例的时候都有自己的、独立的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(用户ID或事务ID)与线程关联起来。
 当func1执行完毕后,栈帧销毁强引用tl没有了。使用弱引用就大概率会减少内存泄漏风险(key为null需单独处理),可以让ThreadLocal对象在执行完毕后顺利被回收且key引用指向null。
 虽然弱引用,保证key指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时,才会去回收整个entry、value,因此弱引用不能100%保证内存不泄露。需要手动调用remove方法来删除ThreadLock对象。底层通过调用expungeStaleEntry( ) 实现清除废旧的entry。

16-Java对象内存布局和对象头

 在HotSpot虚拟机里,对象在堆内存中的存储布局可以分为三个部分:对象头、实例数据和对齐填充。
 对象头可以分为对象标记和类元信息。对象标记会记录哈希码、GC标记、GC次数同步锁标记和偏向锁持有者。

17-Synchronized锁升级

 无锁->偏向锁->轻量级锁->重量级锁

  • 偏向锁。单线程竞争:当一段同步代码一直被同一个线程多次访问,后续访问自动获取锁。偏向锁默认有4s的延迟。竞争出现才释放偏向锁。Java 15取消了偏向锁。
  • 轻量级锁。当有另外线程逐步来竞争锁的时候,偏向锁会升级为轻量级锁。竞争线程尝试CAS更新对象头失败,会等待到全局安全点 (此时不会执行任何代码) 撤销偏向锁。
  • 重量级锁。有大量线程参与锁的竞争,冲突性很高。synchronized的重量级锁基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令,在结束位置插入monitor exit指令。当线程执行到monitor enter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了锁,会在Monitor的owner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor。指向互斥量的指针。

锁和哈希码
 不推荐修改hashCode。当一个对象计算过hashCode后,再也无法进入偏向锁状态。偏向锁过程中遇到一致哈希计算请求,立马撤销偏向模式,膨胀为重量级锁。

  • 无锁状态:对象的hashCode()方法第一次被调用时,JVM会生成对应的identity hash code值并将该值存储到Mark Word中。
  • 偏向锁:线程获取偏向锁,或用线程id和epoch值覆盖identity hash code所在的位置。如果一个对象的hashCode() 方法已被调用过一次之后,这个对象不能被设置偏向锁。为了保证统一对象前后两次调用hashCode() 方法得到的结果一致。
  • 轻量锁:JVM会在当前线程的栈帧中创建一个锁记录(Lock Record)空间,用于存储锁对象的Mark Word拷贝。所以轻量锁可以和identity hash code共存,哈希码和GC年龄保存于此,释放锁后,这些信息写回对象头中,
  • 重量级锁:Mark Word的重量级锁指针指向重量级锁的ObjectMonitor类,里面有字段记录非加锁状态下的Mark Word,释放后也会写回。

18-AQS

AbstractQueuedSynchronizer
 如果共享资源被占用,需要一定的阻塞等待唤醒机制保证锁分配。这个机制基于CLH队列的变体实现,将暂时获取不到锁的线程加入队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象,通过CAS、自旋以及LockSupport.park() 的方式,维护state变量的状态,使并发达到同步效果。

19-读写锁

 ReentrantReadWriteLock。一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。写锁被获取后,可以再次获取写锁或读锁。读锁被获取后,可以再次获取写锁。
 缺点:1. 写锁饥饿; 2. 锁降级。

20-邮戳锁StampedLock

 StampedLock用于解决写锁饥饿问题。读没有结束的时候,也可以加写锁。
特点:

  • 所有获取锁的方法,都返回一个邮戳Stamp,Stamp为零表示获取失败;
  • 所有释放锁的方法,都需要一个邮戳Stamp,这个Stamp必须和成功获取锁时得到的Stamp一致;
  • StampedLock是不可重入的。多次获取锁会导致死锁。

StampedLock三种访问模式:
(1)Reading(读悲观模式);
(2)Writing(写模式);
(3)Optimistic reading(乐观读模式):支持读写并发,乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CRE_MO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值