00.Java基-集合:IO:JUC

本文深入探讨Java集合框架(如ArrayList, LinkedList, Vector, HashSet, TreeSet, HashMap, TreeMap)的底层实现与并发控制,包括JUC中的锁机制、并发工具类和线程池,以及JMM内存模型和死锁排查策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

八大基本类型

byte short int long float double boolean char

包装类

Byte Short Integer Long Float Double Boolean Character

java运行机制

编译 .java的源文件,在用编译期javac,将源程序编译程 .class字节码文件,用虚拟机(解释器)解释执行

javac Hello.java java Hello

方法重载

变量个数与类型不同

垃圾回收机制

堆中对象的管理: 对象空间创建new和释放null

  • 垃圾回收原理
      1. 引用次数法
        • 记录对象引用次数(0表示无用对象)
      1. 引用可达法(搜索算法)

对象创建过程

  1. 分配对象空间、成员变量初始化0/null @1
  2. 执行属性值显示初始化 @2
  3. 执行构造器 @3
  4. 返回对象地址给相关变量 @4

内存空间返回 与 内存空间初始化 之间顺序是可以任意的(—>指令重排)

this

  • this的本质是创建的对象的地址
  • 构造器不是用来创建对象的
  • 构造方法调用其他构造方法: this要放第一行

集合 1

在这里插入图片描述

Collection体系 2

List有序集合 3

ArrayList、LinkedList 4
  • 底层结构
    • ArrayList底层是基于数组实现的,内存地址连续—>查找快
    • Linked是基于链表实现的(运行NULL)
  • 扩容方式
    • ArrayList达到扩容情况,原数据+扩容数组–>涉及扩容与移动(增删慢,查询快)
    • LinkedList 添加、删除节点—>节点–>(增删快,查询慢)
  • other
    • 都实现了List接口,但LinkedList额外实现了Deque接口,所以LinkedList也可以做双端队列
Vector 4

底层数组+synchronized---->效率很低

Set 3

HashSet 4
  • HashSet底层还是HashMap—>可以null
    • map.put(e, PRESENT)==null;插入的本质就是只关心key,value就是一个常亮
  • 无序、不重复(先判断hashcode、在equals判重)
LinkedHashSet 4
  • 有序、线程不安全
TreeSet 4
  • 底层红黑树
  • 自然排序—>实现了Comparable接口

Map体系 2

遍历方式:

​ (1)keySet();遍历key

​ (2)迭代器entrySet().iterator()

​ (3)entrySet()效率高

​ (4)values()只能遍历value

HashMap 3

JDK1.7 HashMap–>数组+链表(链表是为了解决hash冲突)

  • 内部结构:哈希表,不同步,允许null做key-value
  1. hash冲突,会先遍历链表,然后插入链头—>允许key、value为null

    • 通过key与哈希算法与与运算得到数组下标
    • 将key、value封装成Entry对象(1.7Entry,1.8Node对象)
  2. key相同,value便覆盖

  3. 扩容(多线程)可能导致链表循环等并发修改异常

    1. hash种子,是为了让hash值更散列
    2. modcount—>版本号
      1. 遍历的时候删除(可能会异常),通过迭代器去删除,让迭代器的modcount与hashmap的迭代器同步,避免异常
    • 扩容方式
      1. JDK1.7,先判断是否扩容,然后生成Entry对象,头插法插入链表
      2. JDK1.8,先判断当前位置上一个Node类型,是红黑树Node还是链表Node
        • 若红黑树Node,将key、value封装未一个红黑树添加到红黑树当中(判断是否存在当前key,存在则更新value)
        • 若链表Node,将key、value封装成链表Node尾插法插入林彪
          • 插入过程中,先遍历链表,若存在key,更新value
          • 插入到链表后,如果当前链表节点>8,链表转为红黑树
          • key、value封装成Node插入到链表或红黑树后,判断是否扩容

JDK1.7 HashMap–>数组+红黑树

key值<=8时是:数组+链表,key>8时是:数组+红黑树

HashTabale 3

  • 内部结构:哈希表,同步,不允许null做key-value

TreeMap 3

  • 内部结构:二叉树,不同步,允许null做key-value

IO

4大IO抽象类

InputStream/OutputStream字节流、Reader/Writer字符流

  1. 操作数据源/数据目的(读/写)

源: InputStream Reader

目的: OutputStream Writer

  1. 数据是文本还是字节

源:

​ 字节:InputStream

​ 文本:Reader

目的:

​ 字节:OutputStream

​ 文本:Writer

  1. 数据所在设备

    源设备

    ​ 硬盘:文件File开头

    ​ 内存:数组、字符串

    ​ 键盘:System.in

    ​ 网络:Socket

    目的设备

    ​ 硬盘:文件File开头

    ​ 内存:数组、字符串

    ​ 键盘:System.out

    ​ 网络:Socket

  2. 额外功能

转换流:InputStreamReader OutputStreamWriter

高效(缓冲流): Bufferedxxx

对象序列化:bjectInputStream、ObjectOutputStream

数据输出格式: 打印流 PrintStream PrintWriter

  • 字节流
    在这里插入图片描述

    • FileOutputStream有单参和两参(间接/直接路径,是否追加)
  • 字符流Reader、Writer
    在这里插入图片描述

  • InputStreamReader/Writer有单参和两参(间接/直接路径,编码格式)

  • close和flush

    flush 先刷新缓冲区,流对象可以继续使用

    close 先刷新缓冲区,然后释放资源

  • ObjectOutputStream/ObjectInputStream

    • 对象必须实现Serializable接口
    • 不需要序列化的属性,用transient修饰

Properties属性类

public void load(InputStream inStream)从输入流中读取属性列表(键和元素对)
getProperty(String key)   在此属性列表中搜索具有指定键的属性
setProperty(String key, String value)

异常 1

异常体系 2

在这里插入图片描述

Error:程序中无法处理的错误

Exception:程序本身可以捕获,并可以处理的异常

​ 运行时异常:(非受控异常)(不需要预处理,通过规范的代码可以避免产生这种异常)

​ 一般性异常:(受控异常)(这种异常必须显示处理,否则无法编译通过)

异常处理

  • 抛出异常throw,throws
    • throw抛出一个异常对象,并将异常对象传递给调用者,结束当前方法执行:throw new 异常名(参数);
    • 声明抛出异常throws:运用于方法声明上,表示当前方法不处理异常,而是提醒方法调用者处理异常
  • 捕获异常try,catch,finally(异常的捕获,一般按照由大到小顺序,先捕获子异常、在捕获父异常,否则编译错误)

getMessge()和printStackTrace()

  • 获取异常描述信息
    • getMessage()方法,通常用于打印日志
  • 获取异常的堆栈信息
    • printStackTrace(),适合程序调试阶段
  • 面基:

Java异常处理机制的理解

Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为 java.lang.ThrowableThrowable下面又派生了两个子类:ErrorExceptionError: 表示应用程序本身无法克服和恢复的一种严重问题。

Exception: 表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常
系统异常
	系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组下标越界,空指针异常、类转换异常。

普通异常
	普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
  
普通异常必须trycatchthrows抛给上层调用者(checked异常)
系统异常可以不处理(unecked异常)

JUC 1

线程、进程

  • 进程: 运行的程序,至少一个线程
  • 线程: 轻量级进程
  • Java程序,至少主线程+gc线程

并发、并行

  • 并发: 同时做(同一时间)
  • 并行: 一起做(轮流)

JUC核心包

  • java.util.concurrent
    • java.util.concurrent.atomic
    • java.util.concurrent.locks

线程状态

new新生、RUNNABLE运行、BLOCKED阻塞、WATING死等待、TIMED_WATING超时等待、

//创建线程new、就绪状态、阻塞状态B、运行状态、dead

wait、sleep

  • 来自不同类: wait–>Object sleep–>Thread
  • 锁释放: wait()可以释放 sleep()不会释放锁
  • wait必须在同步代码块 sleep任意地方睡
  • wait不需捕获异常 sleep要捕获异常

synchronized、Lock

  • synchronizd

  • static情况锁类Class模板,非static情况锁对象

  • Lock

    自定义锁的内容

线程休眠—>sleep时间到达后线程进入就绪状态、且并不会释放锁

线程礼让:让正在运行的线程暂停,但不阻塞,运行状态转为就绪状态(CPU重新调度)

强制线程执行:join(),当前线程执行完后,其他线程才可以执行(阻塞其他线程)、线程状态getState();

线程优先级:线程之间可以设置优先级来进行优先执行与否,但不一定成功

用户线程、守护线程(setDaemon(true): 虚拟机,确保了用户线程必须执行完毕,不确保守护线程

线程协作(通信)

    1. 管程法
    • 通过缓冲区(容器),对缓冲区适当调用wait() notifyAll(),进行协作
    1. 信号灯
    • 线程之间,设置一个标志位,通过标志位状态,去决定线程运行状态与操作

volatile 2

  • Java虚拟机提供轻量级
    同步机制

  • 保证可见性

    • 一个线程修改共享值写会主存后,其他线程立刻知道自己缓存的共享值失效,需重新读入
  • 不保证原子性

    • 操作不能保证原子性(一致),可能导致值覆盖—>写丢失

      • 工作内存与主内存同步延迟现象导致的可见性问题
    • 解决方案:

      • synchronized、Lock锁
      • 原子包装类Atomic/(Integre/Double)
  • 有序性:禁止指令重排(重排->优化没有依赖性的操作)

    • 指令重排: 禁止指令重排,避免多线程情况下程序乱序执行现象

      • 源代码 --> 编译期优化重排 --> 指令并行重排 --> 内存系统重排 --> 最终执行指令
      • 指令重排,须考虑指令之间的 数据依赖性
    • 内存屏障 <— volatile

  • 修饰关键字: 增加了主线程和线程之间的可见性,只要一个线程修改了内存中的值,其他线程也能马上感知


JMM 内存模型 2

  • 是一种规范

  • 同步的规范

    • 线程解锁前:必须把共享变量刷回主存

    • 线程加锁前:读取主内变量到工作内存—>线程通信:拷贝到工作内存<–>写回主存

    • JMM内模型可见性:主内存的值,写入进程更改后(写回主存),其他线程也会知道更改后的值,并重新得到更改的值

  • JMM三大特性

    • 可见性
    • 原子性
    • 有序性
  • 缓存一致性

    • MESI

      CPU写数据时,如果操作的变量是共享变量,会发出信号通知其他CPU将该内存变量的缓存行设置为无效,其他CPU读取这个变量时,会发现自己缓存改变量的缓存行是无效的,变回从内存中重新读取。主内存值修改了,其他内存值也会马上得到通知
      在这里插入图片描述
      在这里插入图片描述

CAS底层原理:Compare-And-Swap比较并替换 2

  • 判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程原子的
  • CAS是一条CPU的原子指令,不会造成数据不一致问题CAS是下线程安全的
  • CAS是不加锁的,原子性的(本质是比较当前工作内存的值和主存的值,如果相同执行操作,否则一直比较直到主存和工作内存值一致为止)
  • CAS实际上调用了Unsafe类中的本地方法

CAS导致的ABA问题

获取主内存值,到写回主存的过程中,可能该值已经被修改了N次,但是最终修改回原来的值而已 可能线程CAS操作成功,但是可能可能过程是有问题的

  • 解决ABA问题方法:
    • 时间戳原子引用AtomicStampedReference,通过版本号来限制ABA问题
    • LongAdder(CAS机制优化)
      • CAS底层是通过自旋,不断尝试修改值–>耗性能—> LongAdder引入

JUC下Collection 2

并发情况下,一般的集合的操作可能会出现并发修改异常

  • 解决方法
    • 方法一:Vector
      • 本质是在方法上加了synchronized锁–>一次只有线程可以操作–>线程安全、并发性能低
    • 方法二:Collctions.synchronized()
      • 本质是给传入的集合外包装一层 同步机制
    • 方法三:JUC 提供的类CopyOnWrite(List/Set),ConcurrentHashMap
      • 写时复制–>读写分离
      • 写时复制容器,写完指向当前容器,读写使用不同容器

2

  1. 公平锁、非公平锁
  • 公平锁: FIFO先来先服务—>队列
  • 非公平锁: (上来就尝试占有锁,不成功就类似公平锁方式)可插队–>synchronized也是非公平锁
  • Lock lock = new ReentrantLock(true/false); 默认false非公平锁、true公平锁
  • 非公平锁性能(吞吐量)比公平锁大
  1. 可重入锁(递归锁)—>避免死锁
  • 线程可以进入任何一个它已经拥有的锁所同步的代码块
  • ReentrantLock、Synchronized就是可重入锁
  1. 自旋锁: spinlock
  • 尝试获取锁的线程不会立刻阻塞,采用循环的方式不断获取锁–>避免上下文切换的消耗 <-- 循环消耗CPU
  • CAS底层就是自旋锁
  1. 读锁、写锁(独占锁、共享锁)
ReentrantReadWriteLock //读写锁
lock.writeLock().lock(); lock.writeLock().unlock();
lock.readLock().lock();  lock.readLock().unlock();		
  1. Synchronized无法禁止指令重排,但可以保证有序性
  1. 死锁

死锁: 线程之间抱着对方需要的资源不放

  • 互斥条件:一个资源只能被一个线程使用
  • 请求与保持: 一个线程对已有的资源不放,去请求其他资源被阻塞
  • 不剥夺: 进程已获得资源,未释放前,不能强行剥夺
  • 循环等待: 进程之间线程头尾相接循环等待资源

JUC辅助类 2

CountDownLatch

  • 让一些线程阻塞直到另一些线程完成一系列操作才被唤醒

countDownLatch.countDown();

countDownLatch.await();当线程到达预订好的上限时,才开始执行await之后的代码,否则一直死等

CyclicBarrier

  • 开始为0,做加法,直到加到某个值的时侯执行让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障后,屏障才会开门,所有被阻塞的线程继续运行

cyclicBarrier.await(); 线程进入屏障

Semaphore 信号量

  • 用于资源互斥使用与并发线程数的控制
  • acquire();信号量-1
  • release(); 信号量释放

阻塞队列、非阻塞队列 2

BlockingQueue(阻塞队列)接口—>Queue—>Collection
AbstractQueue(非阻塞队列)—>Queue—>Collection
在这里插入图片描述

阻塞队列 BlockingQueue

阻塞队列为空,获取队列元素->阻塞 阻塞队列满,添加元素->阻塞

ArrayBlockQueue数组组成的有界阻塞队列 LinkedBlockingQueue链表组成有界阻塞队列(默认大小Integer.MAX_VALUE相当于无界)

SynchronousQueue 不存储元素的阻塞队列(单个元素的队列)(每一个put操作,都伴随一个take操作)

PriorityBlockQueue支持优先级的无界阻塞队列 DelayQueue 优先级延迟无界阻塞队列

LinkedTransferQueue链表无界阻塞队列 LinkedBlockingDeque链表组成双向阻塞队列

方法类型抛出异常特殊值阻塞超时
插入add(e)队满插入报异常offer(e)插入成功true,失败falseput(e)队满死等插入offer(e,time,unit)
移除remove()队空移除报异常poll()成功返回队首,否则返回空take()队空死等移除poll(time,unit)
检查element()peek()--

Synchronized与Lock区别

  1. Synchronized无需手动释放,代码块执行完后自动解锁,Lock需手动加锁、释放锁

  2. synchronized不可中断,ReentrantLock可以中断与超时方法trylock(long timeout,TimeUnit unit);

  3. synchronized非公平锁、Lock通过构造函数传递布尔值默认非公平锁,true为公平锁

  4. Lock可以绑定多个条件,实现对线程分组和精准唤醒–>可以实现线程之间的循环

    Synchronized只能随机唤醒和全部唤醒

  5. synchronized是JVM层面–关键字,Lock是API层面–具体类

线程池 2

  • 线程实现方式
    • 实例化Thread类
    • 实现Runnable接口
    • 实现Callable接口—>有返回值(通过FutureTask将Callable实现类携带到Thread去启动)
    • 线程池获取—>线程复用、控制最大并发数(类似于JDBC)、减少上下文开销

创建线程池方式

  • Executors.newFixedThreadPool(int i); 定长线程池 <–队列太长,可能堆积大量请求

  • Executor.newSingleThreadExecutor();单线程池 <–队列太长,可能堆积大量请求

  • Executors.newCacheThreadPool();可扩容线程池<–队列长度Integer.MAX_VALUE线程上限太大可能导致OOM

  • Executor.newScheduledThreadPool(int corePoolSize);定时、周期性线程池<—队列长度Integer.MAX_VALUE 线程上限太大

  • 线程池参数

    public ThreadPoolExecutor(
      int corePoolSize, //核心线程数量
      int maximumPoolSize, //最大线程数量
      long keepAliveTime, //空闲线程存活时间
      TimeUnit unit, //存活时间单位
      BlockingQueue<Runnable> workQueue, //任务队列:LinkedBlockingQueue阻塞队列和Synchronized同步阻塞队列
      ThreadFactory threadFactory, //线程工厂
      RejectedExecutionHandler handler //拒绝策略:队列满了且线程大于最大线程数-->开始拒绝策略
    )
    
  • 拒绝策略

    • AbortPolicy: 默认,抛出异常,阻止系统正常运行
    • DiscardPolicy: 直接丢弃
    • CallerRunsPolicy: 将任务回退给调用者
    • DiscardOldestPolicy: 抛弃队列中等待最久的任务

线程池作用

  • 减少创建、销毁线程所消耗的时间

线程池参数设置:分配corePoolSize、maximumPoolSize

  • CPU密集型: 所有的线程都用于任务
  • IO密集型: 给出一半的线程用于IO操作
//线程池运行过程
1.创建线程池,等待提交过来的任务
2.调用execute()添加线程任务
	1.如果正在运行的线程数量小于`核心线程`,马上创建线程运行当前任务
	2.如果正在运行的新城数量大于等于`核心线程`,将任务放入队列中
	3.队列满了,运行的线程数量小于`最大线程数量`,创建非核心线程去运行当前任务
	4.队列满了,运行的线程数量大于等于`最大线程数量`,线程池饱和,启动拒绝策略
3.线程完成任务,从队列去任务,去执行
4.非核心线程空闲一定时间,线程停掉

项目排查JVM问题 2

  • 使用jmap查看jvm中各个区域的使用情况
  • jstack查看线程的运行情况(那些线程阻塞、出现死锁)
  • 通过jstat查看垃圾回收的情况,特别是fullgc,如果fullgc频繁,就得调优了
  • 找到占用CPU最多的线程,定位到具体方法,优化某个方法执行,看能否避免对象创建,从而节省内存

死锁排查 2

jps -l 找到一直运行的线程

jstack 进程号 查看堆栈信息

其他 1

序列化

  • 序列化必须实现Serializable接口
  • 变量被transient修饰,变量不会被序列化
  • static修饰的变量不参与序列化,无论静态变量是否被transient修饰
  • final变量参与序列化、final transient同时修饰:不会序列化
  • 一般要自定义SerialversionUID,避免了修改原来的pojo,导致反序列化失败

static

  • static的意义:创建独立于具体实例对象的域变量或方法(没有创建对象,也能使用属性和调用方法),在内存中只有一份,在类加载过程中,JVM分配一次内存空间
  • 静态代码块:类初次加载时,按static块顺序来执行每个static块且只执行一次
  • 静态方法:不属于实例对象,自然就用不上this关键字了(静态访问静态、非静态可访问非静态+静态)

String底层

实现了:Serializable可序列化接口、Comparable<String>比较大小接口、CharSequence常用方法的接口

String类是final修饰的,数组(jkd8前是字符数组:占两个字节,jdk9后就是byte数组,占一个字节,StringBuffer和StringBuilder也是一样)

StringBuffer线程安全,执行效率低、StringBuilder线程不安全,执行效率高


  • String不可变,变的只是引用的对象,原来的对象依然存在内存中

  • String的value引用的内存区域存储的值,无法改变,但可以改变value引用

  • String可以通过反射的方式,不改变value的引用,而改变value引用下的值(通过反射,先拿到String的value字段,然后改变访问权限,然后设置value字段的值)–通过反射可以修改不可变的对象。

  • String字符串的拼接

    • 常量与常量的拼接,实在编译期
    • 只要有一个是变量(就是new String()就会创建新空间),变量拼接的原理是StringBulider(jdk5之后,之前用StringBuffer)
  • intern()方法

    • 底层调用了本地方法
    • 作用是:当前字符串是否在常量池中存在
      • 存在:直接返回地址
      • 不存在:将堆中字符串的地址复制到字符串常量池中,这样常量池就有了该字符串地址引用
    • new String(“sb”)实际上是创建了两个对象
      • 一个是在堆中创建的对象
      • 一个实在堆中字符串常量池中创建
      • 这两个对象内容一样,但地址不一样

final

  • 修饰类:该类无法继承(编译报错)

  • 修饰方法:不可以重写(编译报错)

  • 修饰变量:

    • 基本数据类型:初始化后,无法改变

      • 成员变量:定义时或构造器中,初始化赋值(编译错误)

      • 局部变量:在使用之前初始化即可

        String a = "Hello1";
        final String b = "Hello";					
        String c = "Hello";
        b1 = b + 1;
        c1 = c + 1;
        System.out.println(a==b1);  //true
        System.out.println(a==c1);	//true
        

        final修饰的变量,编译期就已经知道,所以遇到final修饰的值,直接就会替换成常量值了,而变量只有在运行期间才能确定

    • 引用类型:初始化后,无法改变引用

函数式接口

四大函数式接口:简化编程模型
lambda表达式、链式编程、函数式接口、Stream流式计算

  • 函数式接口interface Function<T, R>
    • 只有一个方法的接口
    • @FunctionalInterface
      函数型接口,可用lambda表达式简化
  • 断定型接口interface Predicate
    • 只有一个输入参数,返回值只能 布尔值
  • 消费型接口interface Consumer
    • 只有一个输入参数,没有返回值
  • 供给型接口interface Supplier
    • 没有参数,只有返回值

Stream流式计算
interface Stream
所有的集合都有一个stream()方法,将集合变成流对象

Lamda表达式

函数式接口—>只包含一个抽象方法,甚至可以去掉参数类型


值传递、引用传递

八种基本数据类型,在栈里面分配内存,属于值传递

引用类型,在存储在堆区,属于引用传递

栈管运行,堆管存储

正则表达式

[a-zA-Z] 范围限定

长度限定:{}

[a-z]{5} 长度5

[a-z]{2,8} 长度2-8

[a-z]{2,} 长度最少2

长度限定符号

预定义符号来完成长度限定

? 零次或一次,等价于{0,1}

+ 一次或多次,等价于{1,}

*零次或多次,等价于{0,}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值