文章目录

  • 1、基础
  • 1.1 异常
  • 1.2 经典问答
  • 2、并发
  • 2.1 基础
  • 2.2 锁
  • 2.3 线程池
  • 2.4 ThreadLocal
  • 3、JVM
  • 3.1 运行时内存区域
  • 3.2 类加载
  • 4、设计模式
  • 4.1 单例模式
  • 4.2 工厂模式
  • 4.3 代理模式
  • 4.4 模板方式模式


1、基础

1.1 异常

  • Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情
    况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;
  • Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;
    也就是说,它表示如果程序运行正常,从不会发生的情况。
  • Java 编译器要求方法必须声明抛出可能发生的受检异常,
    但是并不要求必须声明抛出未被非法参数 捕获的运行时异常。
  • 类转换:ClassCastException 非法参数:IllegalArgumentException 下标越界:IndexOutOfBoundsException 空指针:NullPointerException

1.2 经典问答

  • synchronized原理:使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令;执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进入等待队列中。执行monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。
  • ReentrantLock;ReentrantLock需要显式的获取锁和释放锁,基于AQS(AbstractQueuedSynchronizer 抽象队列同步器)实现。
  • CAS叫做CompareAndSwap,比较并交换,主要是通过处理器的指令来保证操作的原子性,,只有当变量内存值等于旧值时,才会用新值去更新V的值,否则就不会执行更新操作。
  • HashMap主要由数组和链表组成,他不是线程安全的。核心的点就是put插入数据的过程,get查询数据以及扩容的方式。JDK1.7和1.8的主要区别在于头插和尾插方式的修改,头插容易导致HashMap链表死循环,并且1.8之后加入红黑树对性能有提升。
  • 多线程环境怎么使用Map:多线程环境可以使用Collections.synchronizedMap同步加锁的方式,还可以使用HashTable,但是同步的方式显然性能不达标,而ConurrentHashMap更适合高并发场景使用。ConcurrentHashmap在JDK1.7和1.8的版本改动比较大,1.7使用Segment+HashEntry分段锁的方式实现,1.8则抛弃了Segment,改为使用CAS+synchronized+Node实现,同样也加入了红黑树,避免链表过长导致性能的问题。
  • volatile原理:使用volatile声明的变量,可以确保值被更新的时候对其他线程立刻可见。volatile使用内存屏障来保证不会发生指令重排,解决了内存可见性的问题。主内存可以认为就是物理内存,Java内存模型中实际就是虚拟机内存的一部分
  • ThreadLocal原理:ThreadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离,相比于synchronized的做法是用空间来换时间。
  • 线程池原理:核心的参数:最大线程数maximumPoolSize;核心线程数corePoolSize;活跃时间keepAliveTime;阻塞队列workQueue;拒绝策略RejectedExecutionHandler
• AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
• CallerRunsPolicy:只用调用者所在的线程来处理任务
• DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
• DiscardPolicy:直接丢弃任务,也不抛出异常
  • 对称加密算法:DES、AES 非对称加密算法:RSA Hash算法:MD5、SHA-256

2、并发

2.1 基础

  • 所有的变量都存储在主内存中,每个线程还有自己的工作内存,工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的主内存副本拷贝。
  • 线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成。
  • 上下文切换:线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会,就需要轮转使用 CPU。不同的线程切换使用 CPU发生的切换数据等就是上下文切换。
  • 线程调度算法是采用时间片轮转的方式,可以设置线程的优先级。
  • FutureTask 可用于异步获取执行结果或取消执行任务的场景。
  • interrupt 方法终止线程
  • Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一个
    方法,是不能保证共享变量的线程安全性的。
  • volatile 保证内存可见性和禁止指令重排。
  • 在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序。在单线程环境下不能改变程序运行的结果。
  • JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁。(Object类 wait,notify 和 notifyAll)。
  • yield 方法使当前线程从执行状态(运行状态)变为可执行态(就绪状态)

2.2 锁

  • sleep()方法是Thread的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保
    持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。
  • wait()是 Object 类的方法,调用对象的 wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池,只有调用
    对象的 notify()方法(或 notifyAll()方法)时才能唤醒等待池中的线程进入等锁池,如果线程重新获得对象的锁就可以进入就绪状态。
  • wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个。
  • 为了避免 wait 和 notify之间产生竞态条件。
  • Lock 能完成 synchronized 所实现的所有功能。
    -同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块;同步块是更好的选择,因为它不会锁住整个对象。
  • 即一个线程访问对象的同步代码块,另一个线程可以访问该对象的非同步代码。
  • 乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题
  • 乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
  • 悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。

两者实现方式:

  • 乐观锁:使用版本号机制CAS算法实现
  • 悲观锁:实现方式是加锁,对代码块加锁(如Java的synchronized关键字),对数据加锁(如MySQL中的排它锁)

2.3 线程池

每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。

  • newSingleThreadExecutor:创建一个单线程的线程池。
  • newFixedThreadPool:创建固定大小的线程池。
  • CachedThreadPool:创建一个可缓存的线程池;一个任务创建一个线程。
  • execute()方法的返回类型是 void,它定义在Executor 接口中。
  • submit()方法可以返回持有计算结果的 Future 对象,它定义在ExecutorService 接口中。
  • 因为我们定期要向系统中的用户发送短信或者邮件信息进行商品的推广和促销,考虑到用户的数据量会不断增大,为了提高性能,我们就以1000条数据为一个批次,分批从用户表中取出数据,结合spring定时器和线程池进行发送。
  • 我们通常将这种大数据量的操作就通过线程池执行从而提高执行效率。在之前的项目中遇到过要
    将指定的产品图片生成缩略图并且加上水印,考虑到图片的数量比较大,每次要处理的图片在5万张左右,所以我就采用了线程池这项技术,将5万张图片分为多个批次,每批次1000张,然后交给线程池中的多个线程去并行处理,这样就大大缩短了生成缩略图的时间。

2.4 ThreadLocal

每个线程都有一个 ThreadLocal 就是每
个线程都拥有了自己独立的一个变量。

  • 比如你可以用 ThreadLocal 让
    SimpleDateFormat 变成线程安全的,因为那个类创建代价高昂且每次调用都需
    要创建不同的实例所以不值得在局部范围使用它。
  • mybatis里面的使用,ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务。
  • 内部实现:利用当前线程作为句柄获取一个ThreadLocalMap的对象;ThreadLocalMap map = getMap(t);获取的实际上是Thread对象的threadLocals变量。

3、JVM

3.1 运行时内存区域

Java分批获取数据并处理 java 分批查询_数据

  • 线程独有:程序计数器,Java 虚拟机栈;共享:堆,方法区,运行时常量池
  • Java 虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。
  • 堆:所有对象都在这里分配内存,是垃圾收集的主要区域(“GC 堆”)。
  • 方法区:用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 运行时常量池:运行时常量池是方法区的一部分。Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区。

3.2 类加载

  • 类加载过程包含了加载、验证、准备、解析和初始化这 5 个阶段
  • JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。
  • 类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应
    的 Class 对象。

4、设计模式

4.1 单例模式

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

  • 构造私有
  • 以静态方法或者枚举返回实例
  • 确保实例只有一个,尤其是多线程环境
  • 确保反序列化时不会重新构建对象

4.2 工厂模式

工厂设计模式,顾名思义,就是用来生产对象的,在 java 中,万物皆对象。

  • 简单工厂:一个工厂方法,依据传入的参数,生成对应的产品对象;
  • 工厂方法:将工厂提取成一个接口或抽象类,具体生产什么产品由子类决定;
  • 抽象工厂: 为创建一组相关或者是相互依赖的对象提供的一个接口,而不需要指定它们的具体类

4.3 代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活 中常见的中介。

中介隔离作用: 在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到 中介的作用,其特征是代理类和委托类实现相同的接口

按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理(JDK动态代理、CGLib 动态代理)。

4.4 模板方式模式

定义一个操作中的算法的骨架,而将步骤延迟到子类中, 用来解决代码重复的问题。 比如:RestTemplate、JmsTemplate、JpaTemplate