JAVA并发编程学习笔记

本文详细探讨了Java并发编程中的上下文切换原理、减少上下文切换的方法(如无锁并发、CAS算法、线程数量管理和协程),以及避免死锁的策略。还讲解了volatile和synchronized的实现原理,以及Java内存模型和指令重排序的概念。
摘要由CSDN通过智能技术生成

JAVA并发编程

一、并发编程挑战

什么是上下文切换?

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

如何减少上下文切换?

减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。

无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一

些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。

CAS算法:Java的Atomic包使用CAS算法来更新数据,而不需要加锁。

使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这

样会造成大量线程都处于等待状态。

协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

避免死锁的常见方法有哪些?

避免一个线程同时获取多个锁。

避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。

对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

volatile的定义与实现原理是什么?

定义:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。

为了提高处理速度,处理器不会直接和内存进行通信,而是先将系统内存的数据读到处理器内部的缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

synchronized实现同步中,同步方法、静态方法、方法块的锁分别是什么?

·对于普通同步方法,锁是当前实例对象。

·对于静态同步方法,锁是当前类的Class对象。

·对于同步方法块,锁是Synchonized括号里配置的对象。

Java SE 1.6中,锁一共有哪四种状态?

从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。

Java对象到底包含什么?

对象头、对象体和对齐字节。

对象头中包含了Mark Word标记字(64位),Klass Word(64位),数组长度(64位)。

1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;

2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;

3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;

4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和型;

5.对齐字是为了减少堆内存的碎片空间(不一定准确)。

一、Mark Word(标记字)

以上是Java对象处于5种不同状态时,Mark Word中64个位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:

lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。biased_lock和lock一起,表达的锁状态含义如下:

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。

age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。

thread:持有偏向锁的线程ID。

epoch:偏向锁的时间戳。

ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。

ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

我们通常说的通过synchronized实现的同步锁,真实名称叫做重量级锁。但是重量级锁会造成线程排队(串行执行),且会使CPU在用户态和核心态之间频繁切换,所以代价高、效率低。为了提高效率,不会一开始就使用重量级锁,JVM在内部会根据需要,按如下步骤进行锁的升级

1.初期锁对象刚创建时,还没有任何线程来竞争,对象的Mark Word是下图的第一种情形,这偏向锁标识位是0,锁状态01,说明该对象处于无锁状态(无线程竞争它)。

2.当有一个线程来竞争锁时,先用偏向锁,表示锁对象偏爱这个线程,这个线程要执行这个锁关联的任何代码,不需要再做任何检查和切换,这种竞争不激烈的情况下,效率非常高。这时Mark Word会记录自己偏爱的线程的ID,把该线程当做自己的熟人。

3.当有两个线程开始竞争这个锁对象,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象并执行代码,锁对象的Mark Word就执行哪个线程的栈帧中的锁记录。如下图第三种情形。

4.如果竞争的这个锁对象的线程更多,导致了更多的切换和等待,JVM会把该锁对象的锁升级为重量级锁,这个就叫做同步锁,这个锁对象Mark Word再次发生变化,会指向一个监视器对象,这个监视器对象用集合的形式,来登记和管理排队的线程。如下图第四种情形。

二、Klass Word(类指针)
这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

每个Class的属性指针(即静态变量)
每个对象的属性指针(即对象变量)
普通对象数组的每个元素指针
当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
三、数组长度
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。

Java如何实现原子操作?

Java中可以通过锁和循环CAS的方式来实现原子操作。

CAS实现原子操作的三大问题

ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

第3章 Java内存模型

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

从源代码到指令序列的重排序

在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类

型。

1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句

的执行顺序。

2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level

Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值