一天看一点, 迟早进大厂! 秋招面试经验总结, 百分之八十都有用, 不做无用功!
文章目录
- 一天看一点, 迟早进大厂! 秋招面试经验总结, 百分之八十都有用, 不做无用功!
-
- 编译和解释的区别
- String, StringBuffer, StringBuilder
- 重载和重写
- 左值和右值
- 接口和抽象类
- List和Set的区别
- ArrayList和LinkedList的区别
- HashMap和HashTable有什么区别, 底层实现是什么
- volatile
- ConcurrentHashMap的原理
-
- JMM
-
- JMM的三大特性
- 关于同步的规定
- volatile
- synchronized关键字
- 如何实现一个IOC容器
- 什么是字节码?采用字节码的好处是什么?java代码执行过程是什么?
- 虚拟机栈, 本地方法栈, 堆, 方法区
- GC(Garbage Collection)如何判断对象可以被回收
- 在JVM中哪些是共享区?哪些可以作为GC ROOT?
- 等待池和锁池
- 线程的五种状态
- 线程的生命周期
- sleep(), wait(), join(), yield()区别
-
- sleep()和wait
- yield
- join() 执行后线程进入阻塞状态, 例如在线程B中调用A的join(), 那线程B会进入到阻塞队列, 直到线程A结束或中断
- 对线程安全的理解
- Thread和Runnable的区别
- 说说你对守护线程的理解
- 为什么要使用线程
- 为什么要用线程池, 解释下线程池参数
- 线程池处理流程
- 线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程数?
- 线程池中线程复用原理
- JAVA如何开启线程?怎么保证线程安全?
- Volatile和Synchronized有什么区别? Volatile能不能保证线程安全? DCL为什么要加Volatile?
- 简述指令重排
- JAVA线程锁机制是怎样的? 偏向锁, 轻量锁, 重量级锁有什么区别?
- 谈谈你对AQS的理解. AQS如何实现重入锁
- 如何利用多线程对一个数组进行排序
- Spring是什么
- 谈谈你对AOP的理解
- 谈谈你对IOC的理解
- 谈谈你对MVC的理解
- BeanFactory和ApplicationContext有什么区别(不熟)
- ArrayList的扩容规则
- hashCode和equals
- ==和equals
- 重载和重写
- List和Set的区别
- CAS
- currentHashMap
- 1.7版本
- 1.8版本
- HashMap的Put方法
- 深拷贝和浅拷贝
- HashMap的扩容机制
- CopyOnWriteArrayList的底层原理
- 什么是字节码,采用字节码的好处是什么
- JDK, JRE, JVM的区别
- Java中的异常体系
- 在java的异常处理中, 什么时候抛出异常, 什么时候捕获异常
- JAVA线程的五种状态
- 深拷贝和浅拷贝
- HashMap的扩容机制
- CopyOnWriteArrayList的底层原理
- 什么是字节码,采用字节码的好处是什么
- JDK, JRE, JVM的区别
- Java中的异常体系
- 在java的异常处理中, 什么时候抛出异常, 什么时候捕获异常
- JAVA线程的五种状态
- JAVA方法区
编译和解释的区别
- 编译是将代码翻译为目标代码然后执行, 解释是一行一行将代码翻译然后执行
- 因为编译是先翻译全部然后执行, 而解释是一行翻译一行执行, 所以在代码上修改调试解释器可以直接跑而不用重新编译
- 因为有解释器, 所以解释可以在不同平台有相同解释器的机器上允许
- 因为有解释器, 程序在执行的时候解释器也会占用宝贵资源, 所以效率低
String, StringBuffer, StringBuilder
重载和重写
左值和右值
- 右值一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。
- 左值是可寻址的(本质就是用户可访问的存储单元)的变量, 具有持久性
接口和抽象类
- 抽象类是is a(本质)的关系, 接口是like a(行为)
- 定义抽象类的代价更高
List和Set的区别
- List是无序可重复, 可以用迭代器也可以用下标
- Set是有序不可重复, 只能使用迭代器
ArrayList和LinkedList的区别
- ArrayList:
- 基于动态数组连续内存存储, 查询快插入慢(插入大量数据除外,因为链表要创建node, 数组不用)
- LinkedList:
- 基于链表分散存储在内存, 查询慢插入快(插入大量数据除外, 因为链表要创建node, 数组不用)
HashMap和HashTable有什么区别, 底层实现是什么
- HashTable是用sychronized修饰, 线程安全; 而HashMap是线程不安全的
- HashMap运行键值对为null(放在数组0的位置)
volatile
- volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会只去读取缓存的值, 而是去主存直接读取真实值.
ConcurrentHashMap的原理
JDK1.7
- 采用了hashEntry+Segment+分段锁
- 一个segment包括一个HashEntry数组,每个HashEntry又是一个链表结构
- 元素查询: 二次hash, 第一次Hash定位到segment, 第二次Hash定位到元素所在的链表头部
- 分段锁的个数就是并发度, 锁的粒度还是有点大
JDK1.8
- 取消了1.7的segment, 而是采用了hashEntry+CAS+synchronized+volatiled+红黑树, 就像普通1.8 hashMap的样子
- CAS乐观锁, 在修改和赋值的时候用到
- synchronized在hash冲突的时候会用到, 如果hashEntry下有Node元素, 就会开启synchronized代码块, 如果冲突就处理冲突, 如果没有冲突就在尾部插入Node
- volatiled在Node的value和指向下一个Node的链表上修饰, 让其具有可见性
- 红黑树提高查找效率
JMM
https://blog.csdn.net/LYQ20010417/article/details/124138846
JMM的三大特性
1.原子性
一个或多个操作,要么全部执行(执行的过程是不会被任何因素打断的),要么全部不执行。
2.可见性
只要有一个线程对共享变量的值做了修改,其他线程都将马上收到通知,立即获得最新值。
3.有序性
有序性可以总结为:在本线程内观察,所有的操作都是有序的;而在一个线程内观察另一个线程,所有操作都是无序的。前半句指 as-if-serial 语义:线程内似表现为串行,后半句是指:“指令重排序现象”和“工作内存与主内存同步延迟现象”。处理器为了提高程序的运行效率,提高并行效率,可能会对代码进行优化。编译器认为,重排序后的代码执行效率更优。这样一来,代码的执行顺序就未必是编写代码时候的顺序了,在多线程的情况下就可能会出错。
在代码顺序结构中,我们可以直观的指定代码的执行顺序, 即从上到下按序执行。但编译器和CPU处理器会根据自己的决策,对代码的执行顺序进行重新排序,优化指令的执行顺序,提升程序的性能和执行速度,使语句执行顺序发生改变,出现重排序,但最终结果看起来没什么变化(在单线程情况下)。
有序性问题 指的是在多线程的环境下,由于执行语句重排序后,重排序的这一部分没有一起执行完,就切换到了其它线程,导致计算结果与预期不符的问题。这就是编译器的编译优化给并发编程带来的程序有序性问题。
Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行进入。
关于同步的规定
1.线程解锁前,必须把共享变量的值刷新回主内存。
2.线程加锁前,必须将主内存的最新值读取到自己的工作内存。
3.加锁解锁是同一把锁。
在JVM中,栈负责运行(主要是方法),堆负责存储(比如new的对象)。由于JVM运行程序的实体是线程,而每个线程在创建时,JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域。而JAVA内存模型中规定,所有变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问。
但线程对变量的操作(读取赋值等)必须在自己的工作内存中进行。首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后,再将变量写回到主内存。由于不能直接操作主内存中的变量,各个线程的工作内存中存储着主内存中的变量副本,因此,不同的线程之间无法直接访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
volatile
1.volatile简介:
volatile 是 JVM 提供的轻量级的同步机制。volatile 关键字可以保证并发编程三大特征(原子性、可见性、有序性)中的可见性和有序性,不能保证原子性。
2.三大特性
1.保证可见性:
加了volatile关键字修饰的变量,只要有一个线程将主内存中的变量值做了修改,其他线程都将马上收到通知,立即获得最新值。当写线程写一个volatile变量时,JMM会把该线程对应的本地工作内存中的共享变量值刷新到主内存。当读线程读一个volatile变量时,JMM会把该线程对应的本地工作内存置为无效,线程将到主内存中重新读取共享变量。
2.保证有序性
计算机在执行程序时,为了提高计算性能,编译器和处理器常常会对指令进行重排序, volatile使用内存屏障来保证有序性
3.不保证原子性
原子性指的是,当某个线程正在执行某件事情的过程中,是不允许被外来线程打断的。也就是说,原子性的特点是要么不执行,一旦执行就必须全部执行完毕。而volatile是不能保证原子性的,即执行过程中是可以被其他线程打断甚至是加塞的。
所以,volatile变量的原子性与synchronized的原子性是不同的。synchronized的原子性是指,只要声明为synchronized的方法或代码块,在执行上就是原子操作的。而volatile是不修饰方法或代码块的,它只用来修饰变量,对于单个volatile变量的读和写操作都具有原子性,但类似于volatile++这种复合操作不具有原子性。所以volatile的原子性是受限制的。并且在多线程环境中,volatile并不能保证原子性。
synchronized关键字
1.synchronized简介:
synchronized是Java多线程中经常使用的一个关键字。synchronized可以保证原子性、可见性、有序性。它包括两种用法:synchronized 方法和 synchronized 代码块。它可以用来给对象、方法或代码块进行加锁(方法锁也是对象锁)。当它锁定一个方法或者一个代码块时,同一时刻最多只有一个线程可以执行这段代码,其他线程想在此时调用该方法只能排队等候。当它锁定一个对象时,同一时刻最多只有一个线程可以对这个类进行操作,没有获得锁的线程,在该类所有对象上的任何操作都不能进行。
2.锁的类型
- synchronized 是悲观锁的实现,因为 synchronized 修饰的代码,每次执行时都会进行加锁操作,同时只允许一个线程进行操作,所以它是悲观锁的实现。
- synchronized 是非公平锁,并且是不可设置的。这是因为非公平锁的吞吐量大于公平锁,并且是主流操作系统线程调度的基本选择,所以这也是 synchronized 使用非公平锁原因。( 因为 Synchronized 获取锁的行为是不公平的,并非是按照申请对象锁的先后时间分配锁的,每次对象锁被释放时,每个线程都有机会获得对象锁,这样有利于提高执行性能,但是也会造成线程饥饿现象。)
- 同时,synchronized是一个典型的可重入锁,可重入锁最大的作用是避免死锁。
- 可重入锁就是一个线程即使锁还没释放也可以重复获得一个对象的锁
- 如果一个线程获得一段代码的锁, 但是时间片用完, 但是锁并不会被释放, 就保证了下一个时间片还是只能被他自己获取到, 还是会继续执行代码.
3.三大特性
synchronized保证原子性:
1.通过monitorenter和monitorexit指令,可以保证被synchronized修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。
2.即使在执行过程中,由于某种原因,比如CPU时间片用完,线程1放弃了CPU,但是它并没有进行解锁。而由于synchronized的锁是可重入的,这就保证下一个时间片还是只能被他自己获取到,还是会继续执行代码。直到所有代码执行完为止。
synchronized保证可见性:
对于一个被synchronized修饰的变量,在其解锁之前,必须先把此变量同步回主存当中。
synchronized保证有序性:
尽管synchronized无法禁止指令重排和处理器优化,但是可以通过单线程机制来保证有序性。由于synchronized修饰的代码,在同一时刻只能被同一线程访问,从根本上避免了多线程的情况。而单线程环境下,在本线程内观察到的所有操作都是天然有序的,所以synchronized可以通过单线程的方式来保证程序的有序性。
如何实现一个IOC容器
- 首先定义一些注解, 比如DAO层, Service层, Controller层, 以及相应的依赖注入
- 配置文件添加文件路径
- 获取文件信息和内容, 将扫描路径下以.class结尾的文件中放到一个Set集合进行存储
- 遍历这个Set集合, 获取在类上有指定注解的类, 并将其交给IOC容器
- 通过反射来为自动注入的内容进行实例化, 并且存储在IOC容器下某个map结构中(专门存放实例化的对象)
- 真正使用的时候, 获取IOC容器下该map结构中实例对象, 如果这个实例对象中又有其他依赖注入, 就进行递归注入
什么是字节码?采用字节码的好处是什么?java代码执行过程是什么?
- 什么是字节码: 编译程序面对虚拟机编译产生字节码, 因为不同平台都有相同ava虚拟机, 所以字节码是可以在不同平台使用Java虚拟机的中间代码
- 采用字节码的好处:
- 具有较高的运行效率, 因为统一编译为字节码之后再由JVM解释器运行, 比传统的直接由解释器运行效率要高
- 具有较强的移植性, 不同平台都面对一个同样的虚拟机, 只要字节码相同就可以在不同平台上运行, 而较强的移植性是由jvm解释器支持的(解释器本身就具有可移植性)
- java代码执行过程: 不同平台java虚拟机相同, 但jvm的解释器不同, 程序需要先编译成字节码, 然后由jvm的解释器解释为机器码执行(java的编译与解释并存)
- java代码–>编译器–>字节码–>jvm中的解释器–>机器可执行的二进制机器码
虚拟机栈, 本地方法栈, 堆, 方法区
1)程序计数器:用于指示当前线程所执行的字节码执行到了第几行,可以理解为当前线程的行号指示器。每个计数器志勇赖记录一个线程的行号,所以它是线程私有的。
2)虚拟机栈:一个线程的每个方法在执行的同时,会创建一个栈帧,栈帧中存储的有:局部变量、操作栈、动态链接、方法出口等。当方法被调用时,栈帧在JVM栈中入栈,方法执行完成时栈帧出栈。局部变量表中存储方法的相关局部变量,包括各种基本数据类型,对象引用,返回地址等。每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的。
3)本地方法栈:本地方法栈的作用,运行机制,异常类型等方面与虚拟机栈相同,唯一不同时虚拟机栈用来执行java方法的,本地方法栈用来执行native方法的。在许多虚拟机中会将本地方法栈和虚拟机栈放在一起使用。(java是跨平台语言,自然而然会失去对底层的控制,于是想要调用底层方法,就必须使用native方法间接调用底层操作系统的方法(c,c++实现))
4)堆区:堆区是java GC机制最重要的区域。堆区是由线程共享的。在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上所有对象实例都在堆区上分配内存。
5)方法区:方法区是线程共享的,用于存储已经被虚拟机家自爱的类信息、final常量、静态变量、编译器及时编译的代码等。一般不在方法取进行垃圾收集。
GC(Garbage Collection)如何判断对象可以被回收
小牛肉:两种方法,引用计数法和可达性分析法。
所谓引用计数法就是在对象中添加一个字段作为引用计数器,每当有一个地方引用这个对象时,计数器的值就加—;当引用失效时,计数器值就减—;计数器为零的对象就是可以被回收的对象。虽然这个算法简单但是无法解决对象之间的循环引用问题,所以目前主流的JVM用的都是可达性分析算法。
这个算法的基本思路可以理解为—棵多叉树:就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,如果从GC Roots 到某个对象不可达时,则说明此对象是可以被回收的。
1.在虚拟机栈(栈中本地变量表)中引用的对象
2.在本地方法栈中引用的对象
3.在方法区中类的静态变量引用的对象(JDK 1.7开始静态变量从方法区移动到了堆中)
4.在方法区中常量引用的对象
5.JVM内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象((比如
NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器等。这个很好子理解,毕竟如果这些核心的系统类对象被回收了,程序就没办法运行了
6.所有被同步锁(synchronized 关键字)持有的对象
7…
- 可达性算法中的不可达对象并不是立即死亡的, 第一次分析如果该对象不可达
- 没有覆盖了finalize方法, 直接回收
- 覆盖了finalize方法, 用低一级的线程去执行该finalize方法, 然后判断是否可达, 可达就保留, 不可达就回收
在JVM中哪些是共享区?哪些可以作为GC ROOT?
- 堆和方法区是线程共享的, 虚拟机栈, 本地方法栈, 程序计数器是每个线程独有的
垃圾对象就是没被引用的对象, 找到没被引用的对象是比较难找的, 反过来找非垃圾对象相对容易, 非垃圾对象引用的对象也不是垃圾对象, 所以这些非垃圾对象就是GCRoot; GCRoot选择看之前写的
等待池和锁池
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
⒉等待池
当我们调用wait ()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify ()或notifyAl()后等待池的线程才会开始去竞争锁,notify ()是随机从等待池选出一个线程放到锁池,而notifyAllO)是将等待池的所有线程放到锁池当中
线程的五种状态
- 同步阻塞: 运行的线程在获取对象的同步锁时, 若该同步锁被其他线程占用, 则JVM会把该线程放入"锁池"中(简言: 在synchronized中一个对象的锁已经被其他线程占有, 其他的线程分不到就进入锁池)
- 等待阻塞: 在已经获得对象的锁的时候被wait(), 释放锁, 只有其他线程调用notify() 或者notifyAll()的时候会被唤醒, 然后重新去获得锁, 然后运行(简言: 已经获得锁的线程原本正在运行却被强行释放临界资源进入等待池, 然后等待唤醒重新去竞争锁)
- 其他阻塞: 当线程需要去读取文件, 而文件被其他线程占用, 就会发生阻塞(I/O)阻塞. sleep, join或者I/O等待完成
线程的生命周期
1.新建状态(New) ︰新创建了一个线程对象。
2.就绪状态(Runnable)∶线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running) :就绪状态的线程获取了CPU,执行程序代码。
4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
5.死亡状态(Dead) : 线程执行完或者因异常退出了run方法
sleep(), wait(), join(), yield()区别
sleep()和wait
- sleep()方法是Thread类的静态本地方法, wait()则是Object类的本地方法(where)
- sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信。(作用)
- sleep方法不依赖于同步器synchronized, 但是wait需要依赖synchronized(依赖)
- sleep方法不会释放锁, 但是wait会释放锁(锁)
- sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要〈不指定时间需要被别人中断)。(醒)
where 作用 依赖 锁 醒
yield
- yield()执行后线程直接进入就绪状态, 马上释放cpu执行权, 但是依然保留cpu的执行资格, 所以cpu下次进行线程调度还可能会让这个线程去的执行权
- join()执行后线程进入阻塞状态, 例如在线程B中调用线程A的join(), 那线程B会进入阻塞队列, 直到线程A结束或中断结束
join() 执行后线程进入阻塞状态, 例如在线程B中调用A的join(), 那线程B会进入到阻塞队列, 直到线程A结束或中断
例如t1线程正在执行, t2线程调用了t1.join(), 那线程B就会立即进入阻塞队列, 直到线程A结束或者中断线程
对线程安全的理解
- 不是线程安全, 应该是内存安全, 堆是共享内存, 可以被所有线程访问
当多个线程访问一个对象时, 如果不用额外的同步控制或者协调操作, 只是调用这个对象就可以获得正确的结果, 我们说这个对象时线程安全的
- 堆是进程和线程共享的空间, 堆在操作系统对进程初始化的时候分配, 运行过程中也可以向系统要额外的堆, 堆锁存放的内存区域是存放对象实例和数组
- 栈是每个线程独有的, 保存其运行状态和局部变量.每个线程的栈相互独立, 因此栈是线程安全的.
Thread和Runnable的区别
Thread和Runnable实质上是继承关系, 没有可比性. 无论使用Runnable还是Thread, 都会new Thread, 然后执行run方法.
- Thread是实现了Runnable接口的类,使的run支持多线程
- 用法上, 如果有复杂的线程操作需求, 那你就继承Thread, 如果只是简单的执行一个任务就实现Runnable
- 实现Runnable接口的同时,还可以继承其他类,避免Java的单继承性带来局限性。
- Runnable接口可以实现资源共享,Thread无法完成资源共享。
说说你对守护线程的理解
- 定义
- 来为其他所有非守护线程线程提供服务支持
- 在任何情况下, 只有当其他线程全部停止了的时候, 这个线程就必须正常且关闭,那就可以作为守护线程使用
- 由于守护进程的终止自身都是无法控制的, 因此千万不要吧IO, File等重要操作逻辑分配给它, 因为它不靠谱
- 举例: 比如GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
为什么要使用线程
1.在很多程序中,需要多个线程互相同步或互斥的并行完成工作,而将这些工作分解到不同的线程中去无疑简化了编程模型。
2.因为线程相比进程来说,更加的轻量,所以线程的创建和销毁的代价变得更小。
3.线程提高了性能,虽然线程宏观上是并行的,但微观上却是串行。从CPU角度线程并无法提升性能,但如果某些线程涉及到等待资源(比如IO,等待输入)时,多线程允许进程中的其它线程继续执行而不是整个进程被阻塞,因此提高了CPU的利用率,从这个角度会提升性能。
4.在多CPU或多核的情况下,使用线程不仅仅在宏观上并行,在微观上也是并行的。
为什么要用线程池, 解释下线程池参数
1、降低资源消耗; 提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统—分配调优监控。
①newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
②newFixedThreadExecutor(n)
固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③newCacheThreadExecutor(推荐使用)
可缓存线程池, 当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程
线程池处理流程
线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程数?
1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度 ,就无法保留当前的任务
了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程(当没有任务时,阻塞核心线程)进入wait状态,释放cpu资源。阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂
起,从而维持核心线程的存活、不至于一直占用cpu资源。
(简言: 阻塞队列可以对任务队列进行存储(生产者-消费者模型), 可以在阻塞队列为空的时候自动将线程池中的线程阻塞挂起节约CPU资源, 如果是任务队列不为空时就自动唤醒线程)
2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。
就好比一个企业里面有10个(core)正式工的名额,最多招10个正式工,要是任务超过正式工人数
(task > core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。
线程池中线程复用原理
- 线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。
- 在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start()来创建新线程,而是让每个线程去执行一个"循环任务"”,在这个"循环任务"中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。
( 解读: 直接调用run方法就是说在该线程单纯调用一个方法, 而start是创建了一个线程并且运行了run方法, 但是多线程中线程本来就只需要维护自己任务的运行而不需要开新进程了, 所以直接跑run就行)
JAVA如何开启线程?怎么保证线程安全?
- 开启线程 : 继承Thread类, 重写run方法; 实现Runnable接口, 实现run方法; 通过线程池开启线程
- 保证线程安全: 加锁
- JVM的锁, 也就是Synchronize关键字
- JDK提供的各种锁: 非公平锁,公平锁, 可重入锁
Volatile和Synchronized有什么区别? Volatile能不能保证线程安全? DCL为什么要加Volatile?
-
Synchronize能保证原子性, 有序性, 可见性, 是线程安全的,而且Synchronize是加锁的; Volatile能保证有序性和可见性, 不能保证原子性, 是线程不安全的; Volatile适合一写多读的场景
-
什么是DCL(Double Check Lock)
这里有两个if(null == singleDemo1) 来检查
问题: 为什么不把synchronized代码块放到第一个if前面?
-
- 因为每一个线程运行这段代码都要先判断第一个check, 如果像这样做就相当于每个线程都要来获取锁才能执行, 反之线程如果不满足第一个if自动就会去执行下面的不需要考虑线程安全的代码, 效率更高
- 第二个check是判断之前已经先获得锁的线程是否已经把我需要的内容做完了, 避免重复创建
再解释:
-
- 第一个check来看“要经过锁才能完成的任务”是否已经完成,是就直接执行其他代码
- 否的话多线程就来竞争锁,竞争胜利的进行synchronized代码块
- 第二个check“要经过锁才能完成的任务”是否已经被其他线程完成了,因为竞争锁的这段时间可能其他线程已经将此任务完成
- 如果是,就执行其他代码,如果不是,就完成此任务
加Volatile防止指令重排
简述指令重排
因为CPU和内存的效率问题, 为了提高运行效率往往会进行指令重排, 但是指令重排就会影响多线程运行
例子 : 比如 integer i=8;
- 分为三个步骤: 分配内存, 对象初始化, 建立指针对应关系
- 但是因为指令重排会变成: 1.分配内存, 2.建立指针对应关系, 3.对象初始化
- 如果不管指令重排, 那么其他线程就会在2和3之间就直接读取到错误数据0(此时指针已经建立, 但是正确数据却还没有到位) 所以有时候需要使用volatile来阻止指令重排
JAVA线程锁机制是怎样的? 偏向锁, 轻量锁, 重量级锁有什么区别?
- JAVA的锁机制就是在线程竞争激烈程度升级而升级
- 偏向锁:是指一段同步代码,只有一个单线程所访问,那么该线程会自动获取锁;降低获取锁的代价
- 轻量级锁:是指当前锁处于偏向锁状态的时候,被多个线程所访问,偏向锁就会升级为轻量级锁,但只有一个线程能获得锁用使用权,其他线程会通过自旋的形式尝试获取锁,不会阻塞,
- 提高性能重量级锁:是指当前锁处于轻量级锁状态的时候,被多个线程所访问时,但只有一个线程能获得锁用使用权,其他线程会通过自旋的形式尝试获取锁,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。
谈谈你对AQS的理解. AQS如何实现重入锁
1、AQS是一个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。
2、在AQS中,维护了一个信号量state和一个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而state就像是一个红绿灯,用来控制线程排队或者放行的。在不同的场景下,有不同的意义。
3、在可重入锁这个场景下,state就用来表示加锁的次数。0标识无锁,每加一次锁,state就加1。释放锁state就减1。
如何利用多线程对一个数组进行排序
使用归并排序将大问题变为多个小问题, 每个小问题又用fork/join进行线程开启, 多线程执行小任务, 加快运行效率
Spring是什么
- Spring 是一个轻量级的控制翻转(IOC)和面向切面(AOP)的容器框架
- 从大小与开销两方面而言Spring都是轻量级的
- 包含并且管理Bean的配置和生命周期,又通过控制反转和依赖注入来达到松耦合, 在这个意义上是一个Bean容器,
- 提供了面向切面编程的丰富支持, 允许通过分离应用的业务逻辑和系统级 服务进行内聚性的开发
- 将简单的组件配置, 组合为复杂的应用, 这个意义上是一个框架
谈谈你对AOP的理解
- 系统是由许多不同的组件所组成的, 每一个组件各负责着一些功能, 除了主要业务功能之外, 有些组件比如日志, 事务管理和安全等服务会水平散布到所有对象层次中去,
- OOP运行从上到下的关系但是并不适合定义从左到右的关系, 所以如果仍然按照OOP设计就会有大量代码的重复; 如果使用AOP就会将这些功能封装为一个切面, 然后注入到目标对象中, 而且AOP还可以对对象进行增强, 去做一些额外的事情.
谈谈你对IOC的理解
容器概念, 控制翻转, 依赖注入
- (容器概念)IOC容器中存有自己配置获取的一些Bean(比如@Repository, @Service, @Controller), 在项目启动时使用反射创建对应的对象放到map中 ;(依赖注入) 这时候map中就有各种对象了, 接下来通过bean的配置文件中去获取xml文件中该bean节点下的, 根据其ref属性进行依赖注入, 注入到对应的注解下(@Autowired或者@Resource)
- 控制翻转: 控制翻转就是一个概念, 就是对象通过IOC容器和依赖注入自动进行装配, 就相当于原本属于程序员对对象创建的控制权交给了IOC
- 总结来说, 就是IOC通过容器概念, 控制翻转和依赖注入成为了整个系统的关键核心, 它起到一种类似"粘合剂"的作用, 把系统中的所有对象粘合起来一起发挥作用.
谈谈你对MVC的理解
MVC是Model-View-Controler的简称,即模型-视图-控制器。MVC是一种设计模式,它强制性的把应用程序的输入、处理和输出分开。
MVC中的模型、视图、控制器分担着不同的任务:
- 视图:视图是用户看到并能与之交互的界面。视图向用户显示相关的数据,并接受用户的输入。视图不能进行任何业务逻辑处理。例如我们写的html静态页面.jsp动态页面.这柴最终响应给浏览器的页面都是视图。通赏视图是绒据摸型数据来创建的;
- 模型:模型表示业务数据和业务处理。Web应用中用于处理数据逻辑的部分,相当于JavaBean,包括Service层和Dao层。Service员用于和数据库联动,放置业务逻辑代码,处理数据库的增删改查,Dao层用于放各种接口,以备调用;一个模型能为多个视图提供数据.这提高了应用程序的重用性。
- 控制器:当用户单击web页面中的提交按钮时,控制器接受请求并调用相应的模型去处理请求。在Web应用中,简而言之,就是Servlet,或者SpringMVC框架中加了注解@Controller的方法(实际上一个方法就相当于一个对应的Servlet) 。
然后,根据处理的结果调用相应的视图来显示处理的结果。
MVC的处理过程:首先,用户通过视图进行交互,视图将用户的请求发送给控制器,控制器调用相应的模型来进行业务处理,处理的结果又发送给控制器,控制器调用相应的视图来显示结果,用户就得到了处理结果。
BeanFactory和ApplicationContext有什么区别(不熟)
ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
①继承MessageSource,因此支持国际化
②资源文件访问,如URL和文件(ResourceLoader)
③载入多个(有继承关系)上下文(及同时加载多个配置文件),使得每一个上下文都专注于一个特定的层次
④提供在监听器中注册bean的事件;
ArrayList的扩容规则
无参构造容量为0, 第一次扩容+10, 后续扩容都是之前的1.5倍
1.5倍原因, 是
, 原容量右移一位作为增加量
hashCode和equals
-
- hashMap在get数据的时候先回比较hashCode, 在hashCode相同的情况下才会去用equals对key进行比较
- equals和hashCode依据name来进行唯一判断
- 泛型继承
==和equals
-
- ==:如果是基本数据类型,比较是值,如果是引用类型,比较的是引用地址
- equals: 具体看各个类重写equals方法之后的比较逻辑,比如String类,虽然是引用类型,但是sting类中重写了equals方法,方法内部比较的是字符串中的各个字符是否全部相等。
重载和重写
-
- 重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
- 重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
List和Set的区别
-
- List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用lterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素
- Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用lterator接口取得所有元素,在逐一遍历各个元素I
CAS
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,如果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
java CAS详解_奔跑灬小熊的博客-CSDN博客_cas java
【Java多线程】CAS 详解及Java中的实现_码不停蹄的_Mars的博客-CSDN博客_cas java
-
悲观锁
- 悲观锁就是认定多线程的时候一定会出现冲突, 所以用锁锁死
-
乐观锁
- 乐观锁假定一般没有其他线程冲突, 利用CAS和版本更新来实现
- CAS是比较交换, 一般涉及三个值: 内存位置, 预期值, 新值
-
乐观锁是对应读多写少, 悲观锁是对应写少读多
currentHashMap
-
1.7 的currentHashMap 是双层数组, 需要两次hash查找才能查到数据, 第一层是一个segment, segment每个格子里存有一个小hashMap
每一个格子可以由一个线程操作
-
1.8的就不是双层数组
-
将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法
1.7版本
-
- 1.7版本的ConcurrentHashMap是基于Segment分段实现
- 每个Segment相对于一个小型的HashMap
- 每个segment内部会进行扩容,和HashMap的扩容逻辑类似
- 先生成新的数组,然后转移元素到新数组中
- 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
1.8版本
- 1.8版本的ConcurrentHashMap不再基于segment实现
- 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
- 如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
- ConcurrentHashMap是支持多个线程同时扩容的
- 扩容之前也先生成一个新的数组
- 在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作v才
HashMap的Put方法
1.根据Key通过哈希算法与气运算得出数组下标
2.如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置3.如果数组下标位置元素不为空,则要分情况讨论
a.如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
b.如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
i.如果是红黑树Node,则将xey和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
ii.如果此位置上的Node对象是链表节点,则将key和value封装为一个链表Node并通过尾插法插入到铤表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么则会将该链表转成红黑树
深拷贝和浅拷贝
深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。
-
- 浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象
- 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的属性指向的不是同一个对象
- 浅拷贝: 基本数据类型+实例对象的地址(这时浅拷贝的两个实例对象地址是同一个, 也就是同一个实例); 深拷贝: 基本数据类型+实例对象地址下的数据(也就是将实例对象也拷贝了一份)
HashMap的扩容机制
1.7版本
1.先生成新数组
2.遍历老数组中的每个位置上的链表上的每个元素
3.取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
4.将元素添加到新数组中去
5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
1.8版本
1.先生成新数组
2.遍历老数组中的每个位置上的链表或红黑树
3,如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
4.如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
a.统计每个下标位置的元素个数
b.如果该位置下的元素个数超过了8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置c.如果该位置下的元素个数没有超过8,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置将新数组值给HashMap对象的table属
CopyOnWriteArrayList的底层原理
-
- 首先CopyOnWrteArayuist内部也是用过数组来实现的,在向CopyOnWiteArraylist添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
- 并且,写操作会加锁,防止出现并发写入丢失数据的问题
- 写操作结束之后会把原数组指向新数组
- CcopyOnwiteArayist允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CcopyOnWriteAraylist会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景
什么是字节码,采用字节码的好处是什么
编译器(avac)将Java源文件r( java)文件编译成为字节码文件( .class),可以做到一次编泽到处运行,windows上编译好的class文件,可以直接在Linux上运行,通过这种方式欧到跨平台,不过Java的跨平台有一个前提条件,就是不同的操作系统上安装的JDK或JRE是不一样的,虽然字节码是通用的,但是需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各自的JDK或JRE。
采用字节码的好处,一方面实现了跨平台,另外一方面也提高了代码执行的性能,编泽器在编译源代码时可以做一些编译期的优化,比如锁消除、标量替换、方法内联等。
JDK, JRE, JVM的区别
JDK
JDK ( Java SE Development Kit ), Java 标准的开发包,提供了编译、运行 Java 程序所需要的各种工具和资源 ,包括了 Java 编译器、 Java 运行时环境、以及常用的 Java 类库等。
JRE
JRE ( Java Runtime Environment ) , Java 运行时环境,用于解释执行 Java 的字节码文件 。普通用户只需要安装 JRE 来运行 Java 程序即可,而作为一名程序员必须安装 JDK ,来编译、调试程序。
JVM
JVM ( Java Virtual Mechinal ), Java 虚拟机,是 JRE 的一部分。 它是整个 Java 实现跨平台的核心 ,负责解释执行字节码文件,是可运行 Java 字节码文件的虚拟计算机。所有平台上的 JVM 向编译器提供相同的接口,而编译器只需要面向虚拟机,生成虚拟机能识别的代码,然后由虚拟机来解释执行。当使用 Java 编译器编译 Java 程序时,生成的是与平台无关的字节码,这些字节码只面向 JVM 。也就是说JVM 是运行 Java 字节码的虚拟机。不同平台的 JVM 是不同的,但是他们都提供了相同的接口。 JVM 是 Java 程序跨平台的关键部分,只要为同平台实现了相同的虚拟机,编译后的 Java 字节码就可以在该平台上运行。
Java中的异常体系
-
- Java中的所有异常都来自顶级父类Throwable。
- Throwable下有两个子类Exception和Error。
- Error表示非常严重的错误,比太如Liav.ang.StackOvefFlowEror(堆栈溢出)和Java.lang.OutOofMemor)Eror(内存错误),通常这些错误出现时,仅仅想靠程序自己是解决不了的,可能是虚拟机、磁盘、操作系统层面出现的问题了,所以通常也不建议在代码中去捕获这些Error,因为捕获的意义不大,因为程序可能已经根本运行不了了。
- Excepion表示异常,表示租序出现kxception时,是可以靠程序自己来解决的,比如MulPointerException.llegalAccessException等,我们可以捕获这些异常来做特殊处理。
- Exception的子类通常又可以分为RuntimeException和非RuntimeException两类
- RurTimeException表示运行期异常,表示这个异常是在代码运行过程中抛H出的,这些异常是非检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生,比如NullPointerException、IndexOutOfBoundsException等。
- 非RuntimeException表示非运行期异常,也就是我们常说的检查异常,是必须进行处理的异常,如果不处理,程序就不能检查异常通过。如OException、SQLException等以及用户自定义的Exception异常。
- 空指针异常, 类未找到异常, 类强制转换异常, 文件未找到异常, 数组下标越界异常
在java的异常处理中, 什么时候抛出异常, 什么时候捕获异常
-
- 异常相当于一种提示,如果我们抛出异常,就相当于告诉上层方法,我抛了一个异常,我处理不了这个异常,交给你来处理,而对于上层方法来说,它也需要决定自己能不能处理这个异常,是否也需要交给它的上层。
- 所以我们在写一个方法时,我们需要考虑的就是,本方法能否合理的处理该异常,如果处理不了就继续向上抛出异常,包括本方法中在调用另外一个方法时,发现出现了异常,如果这个异常应该由自己来处理,那就捕获该异常并进行处理。
JAVA线程的五种状态
深拷贝和浅拷贝
深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。
-
- 浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象
- 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的属性指向的不是同一个对象
- 浅拷贝: 基本数据类型+实例对象的地址(这时浅拷贝的两个实例对象地址是同一个, 也就是同一个实例); 深拷贝: 基本数据类型+实例对象地址下的数据(也就是将实例对象也拷贝了一份)
HashMap的扩容机制
1.7版本
1.先生成新数组
2.遍历老数组中的每个位置上的链表上的每个元素
3.取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
4.将元素添加到新数组中去
5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
1.8版本
1.先生成新数组
2.遍历老数组中的每个位置上的链表或红黑树
3,如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
4.如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
a.统计每个下标位置的元素个数
b.如果该位置下的元素个数超过了8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置c.如果该位置下的元素个数没有超过8,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置将新数组值给HashMap对象的table属
CopyOnWriteArrayList的底层原理
-
- 首先CopyOnWrteArayuist内部也是用过数组来实现的,在向CopyOnWiteArraylist添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
- 并且,写操作会加锁,防止出现并发写入丢失数据的问题
- 写操作结束之后会把原数组指向新数组
- CcopyOnwiteArayist允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CcopyOnWriteAraylist会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景
什么是字节码,采用字节码的好处是什么
编译器(avac)将Java源文件r( java)文件编译成为字节码文件( .class),可以做到一次编泽到处运行,windows上编译好的class文件,可以直接在Linux上运行,通过这种方式欧到跨平台,不过Java的跨平台有一个前提条件,就是不同的操作系统上安装的JDK或JRE是不一样的,虽然字节码是通用的,但是需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各自的JDK或JRE。
采用字节码的好处,一方面实现了跨平台,另外一方面也提高了代码执行的性能,编泽器在编译源代码时可以做一些编译期的优化,比如锁消除、标量替换、方法内联等。
JDK, JRE, JVM的区别
JDK
JDK ( Java SE Development Kit ), Java 标准的开发包,提供了编译、运行 Java 程序所需要的各种工具和资源 ,包括了 Java 编译器、 Java 运行时环境、以及常用的 Java 类库等。
JRE
JRE ( Java Runtime Environment ) , Java 运行时环境,用于解释执行 Java 的字节码文件 。普通用户只需要安装 JRE 来运行 Java 程序即可,而作为一名程序员必须安装 JDK ,来编译、调试程序。
JVM
JVM ( Java Virtual Mechinal ), Java 虚拟机,是 JRE 的一部分。 它是整个 Java 实现跨平台的核心 ,负责解释执行字节码文件,是可运行 Java 字节码文件的虚拟计算机。所有平台上的 JVM 向编译器提供相同的接口,而编译器只需要面向虚拟机,生成虚拟机能识别的代码,然后由虚拟机来解释执行。当使用 Java 编译器编译 Java 程序时,生成的是与平台无关的字节码,这些字节码只面向 JVM 。也就是说JVM 是运行 Java 字节码的虚拟机。不同平台的 JVM 是不同的,但是他们都提供了相同的接口。 JVM 是 Java 程序跨平台的关键部分,只要为同平台实现了相同的虚拟机,编译后的 Java 字节码就可以在该平台上运行。
Java中的异常体系
-
- Java中的所有异常都来自顶级父类Throwable。
- Throwable下有两个子类Exception和Error。
- Error表示非常严重的错误,比太如Liav.ang.StackOvefFlowEror(堆栈溢出)和Java.lang.OutOofMemor)Eror(内存错误),通常这些错误出现时,仅仅想靠程序自己是解决不了的,可能是虚拟机、磁盘、操作系统层面出现的问题了,所以通常也不建议在代码中去捕获这些Error,因为捕获的意义不大,因为程序可能已经根本运行不了了。
- Excepion表示异常,表示租序出现kxception时,是可以靠程序自己来解决的,比如MulPointerException.llegalAccessException等,我们可以捕获这些异常来做特殊处理。
- Exception的子类通常又可以分为RuntimeException和非RuntimeException两类
- RurTimeException表示运行期异常,表示这个异常是在代码运行过程中抛H出的,这些异常是非检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生,比如NullPointerException、IndexOutOfBoundsException等。
- 非RuntimeException表示非运行期异常,也就是我们常说的检查异常,是必须进行处理的异常,如果不处理,程序就不能检查异常通过。如OException、SQLException等以及用户自定义的Exception异常。
- 空指针异常, 类未找到异常, 类强制转换异常, 文件未找到异常, 数组下标越界异常
在java的异常处理中, 什么时候抛出异常, 什么时候捕获异常
-
- 异常相当于一种提示,如果我们抛出异常,就相当于告诉上层方法,我抛了一个异常,我处理不了这个异常,交给你来处理,而对于上层方法来说,它也需要决定自己能不能处理这个异常,是否也需要交给它的上层。
- 所以我们在写一个方法时,我们需要考虑的就是,本方法能否合理的处理该异常,如果处理不了就继续向上抛出异常,包括本方法中在调用另外一个方法时,发现出现了异常,如果这个异常应该由自己来处理,那就捕获该异常并进行处理。
JAVA线程的五种状态
线程的五种状态:
1)新建状态(New):线程对象实例化后就进入了新建状态。
2)就绪状态(Runnable):线程对象实例化后,其他线程调用了该对象的start()方法,虚拟机便
会启动该线程,处于就绪状态的线程随时可能被调度执行。
3)运行状态(Running):线程获得了时间片,开始执行。只能从就绪状态进入运行状态。
4)阻塞状态(Blocked):线程因为某个原因暂停执行,并让出CPU的使用权后便进入了阻塞状
态。
等待阻塞:调用运行线程的wait()方法,虚拟机会把该线程放入等待池。
同步阻塞:运行线程获取对象的同步锁时,该锁已被其他线程获得,虚拟机会把该线程放入锁
定池。
其他阻塞:调用运行线程的sleep()方法或join()方法,或线程发出I/O请求时,进入阻塞状态。
5)结束状态(Dead):线程正常执行完或异常退出时,进入了结束状态。
JAVA方法区
https://blog.csdn.net/A_art_xiang/article/details/118568601