免费背景图片素材网站,漂亮公司网站源码打包下载,室内设计8年熬不起了,wordpress添加广告位文章目录 0.Java 中有 8 种基本数据类型1. 为什么浮点数运算会丢失精度#xff1f;如何解决#xff1f;2. 面向对象的三大特征2.1 封装2.2 继承2.3 多态 3. 深拷贝和浅拷贝的区别#xff1f;什么是引用拷贝#xff1f;4. equals方法与“”方法4.1 4.2 equals方法 5.hashcod… 文章目录 0.Java 中有 8 种基本数据类型1. 为什么浮点数运算会丢失精度如何解决2. 面向对象的三大特征2.1 封装2.2 继承2.3 多态 3. 深拷贝和浅拷贝的区别什么是引用拷贝4. equals方法与“”方法4.1 4.2 equals方法 5.hashcode与equals及对象相等6. 为什么重写 equals() 时必须重写 hashCode() 方法7. 字符串常量池作用8. TCP/IP四层与七层模型9.HashSet如何判断元素是否存在10. 程序计数器为什么是私有的11. 虚拟机栈和本地方法栈为什么是私有的?12. 关键字volatile12.1 volatile如何保证变量的可见性12.2 如何禁止指令重排序 12. 关键字synchronized的三个使用场景12.1 修饰实例方法12.2 修饰静态方法12.3 修饰代码块12.4 总结12.5 静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥么 13.synchronized 和 volatile 有什么区别14. ReentrantLock是什么14.1 公平锁和非公平锁的区别是什么 15.ThreadLocal 内存泄露问题是怎么导致的?16.线程池16.1 使用线程池的好处16.2 如何创建线程池16.2.1 通过ThreadPoolExecutor构造函数来创建推荐16.2.2 通过 Executor 框架的工具类 Executors 来创建 16.3 为什么不推荐使用内置线程池16.4 线程池常见参数有哪些16.4 线程池4中拒绝策略16.5 线程池阻塞队列有哪些 17.AOT 有什么优点为什么不全部使用 AOT 呢 每个工程师都应该有一个自己不断更新的八股文知识宝库。 ps:内容摘抄于各种博客、个人网站、八股文集合等等是原创也是转载就是自己的一个笔记。 0.Java 中有 8 种基本数据类型
分别为6 种数字类型
4 种整数型byte、short、int、long2 种浮点型float、double1 种字符类型char1 种布尔型boolean。
1. 为什么浮点数运算会丢失精度如何解决
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的而且计算机在表示一个数字时宽度是有限的无限循环的小数存储在计算机时只能被截断所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。 // 0.2 转换为二进制数的过程为不断乘以 2直到不存在小数为止 // 在这个计算过程中得到的整数部分从上到下排列就是二进制的结果。 0.2 * 2 0.4 - 0 0.4 * 2 0.8 - 0 0.8 * 2 1.6 - 1 0.6 * 2 1.2 - 1 0.2 * 2 0.4 - 0发生循环 BigDecimal 可以实现对浮点数的运算不会造成精度丢失。通常情况下大部分需要浮点数精确运算结果的业务场景比如涉及到钱的场景都是通过 BigDecimal 来做的。
2. 面向对象的三大特征
2.1 封装
封装是指把一个对象的状态信息也就是属性隐藏在对象内部不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息也就是属性但是可以通过遥控器方法来控制空调。如果属性不想被外界访问我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法那么这个类也没有什么意义了。就好像如果没有空调遥控器那么我们就无法操控空凋制冷空调本身就没有意义了。
2.2 继承
不同类型的对象相互之间经常有一定数量的共同点。例如小明同学、小红同学、小李同学都共享学生的特性班级、学号等。同时每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好小红的性格惹人喜爱小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术新类的定义可以增加新的数据或新的功能也可以用父类的功能但不能选择性地继承父类。通过使用继承可以快速地创建新的类可以提高代码的重用程序的可维护性节省大量创建新类的时间 提高我们的开发效率。关于继承如下 3 点请记住 子类拥有父类对象所有的属性和方法包括私有属性和私有方法但是父类中的私有属性和方法子类是无法访问只是拥有。 子类可以拥有自己属性和方法即子类可以对父类进行扩展。 子类可以用自己的方式实现父类的方法。
2.3 多态
父类引用指向子类对象。
多态的特点:
对象类型和引用类型之间具有继承类/实现接口的关系引用类型变量发出的方法调用的到底是哪个类中的方法必须在程序运行期间才能确定多态不能调用“只在子类存在但在父类不存在”的方法如果子类重写了父类的方法真正执行的是子类覆盖的方法如果子类没有覆盖父类的方法执行的是父类的方法。
3. 深拷贝和浅拷贝的区别什么是引用拷贝
浅拷贝浅拷贝会在堆上创建一个新的对象区别于引用拷贝的一点不过如果原对象内部的属性是引用类型的话浅拷贝会直接复制内部对象的引用地址也就是说拷贝对象和原对象共用同一个内部对象。深拷贝深拷贝会完全复制整个对象包括这个对象所包含的内部对象。简单来说引用拷贝就是两个不同的引用指向同一个对象。
4. equals方法与“”方法
4.1
对于基本数据类型比较的都是值是否相等。 对于引用类型比较的是引用对象的地址是否相同。
4.2 equals方法
equals() 不能用于判断基本数据类型的变量只能用来判断两个对象是否相等。equals()方法存在于Object类中而Object类是所有类的直接或间接父类因此所有的类都有equals()方法。
equals() 方法存在两种使用情况
类没有重写 equals()方法通过equals()比较该类的两个对象时等价于通过“”比较这两个对象使用的默认是 Object类equals()方法。类重写了 equals()方法一般我们都重写 equals()方法来比较两个对象中的属性是否相等若它们的属性相等则返回 true(即认为这两个对象相等)。
5.hashcode与equals及对象相等
hashCode() 定义在 JDK 的 Object 类中这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是Object 的 hashCode() 方法是本地方法也就是用 C 语言或 C 实现的。
如果两个对象的hashCode 值相等那这两个对象不一定相等哈希碰撞。如果两个对象的hashCode 值相等并且equals()方法也返回 true我们才认为这两个对象相等。如果两个对象的hashCode 值不相等我们就可以直接认为这两个对象不相等。
6. 为什么重写 equals() 时必须重写 hashCode() 方法
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的那这两个对象的 hashCode 值也要相等。如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象hashCode 值却不相等。
总结
equals 方法判断两个对象是相等的那这两个对象的 hashCode 值也要相等。两个对象有相同的 hashCode 值他们也不一定是相等的哈希碰撞。
7. 字符串常量池作用
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串String 类专门开辟的一块区域主要目的是为了避免字符串的重复创建。
8. TCP/IP四层与七层模型
1、应用层 2、传输层 3、网络层 4、网络接口层
9.HashSet如何判断元素是否存在
HashSet的add()方法只是简单的调用了HashMap的put()方法并且判断了一下返回值以确保是否有重复元素。直接看一下HashSet中的源码
// Returns: true if this set did not already contain the specified element
// 返回值当 set 中没有包含 add 的元素时返回真
public boolean add(E e) {return map.put(e, PRESENT)null;
}而在HashMap的putVal()方法中也能看到如下说明
// Returns : previous value, or null if none
// 返回值如果插入位置没有元素返回null否则返回上一个元素
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
...
}也就是说在 JDK1.8 中实际上无论HashSet中是否已经存在了某元素HashSet都会直接插入只是会在add()方法的返回值处告诉我们插入前是否存在相同元素。
10. 程序计数器为什么是私有的
程序计数器主要有下面两个作用 字节码解释器通过改变程序计数器来依次读取指令从而实现代码的流程控制如顺序执行、选择、循环、异常处理。 在多线程的情况下程序计数器用于记录当前线程执行的位置从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
需要注意的是如果执行的是 native 方法那么程序计数器记录的是 undefined 地址只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
所以程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
11. 虚拟机栈和本地方法栈为什么是私有的?
虚拟机栈 每个 Java 方法在执行之前会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。本地方法栈 和虚拟机栈所发挥的作用非常相似区别是虚拟机栈为虚拟机执行 Java 方法 也就是字节码服务而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
所以为了保证线程中的局部变量不被别的线程访问到虚拟机栈和本地方法栈是线程私有的。
12. 关键字volatile
12.1 volatile如何保证变量的可见性
在 Java 中volatile 关键字可以保证变量的可见性如果我们将变量声明为 volatile 这就指示 JVM这个变量是共享且不稳定的每次使用它都到主存中进行读取。
volatile 关键字能保证数据的可见性但不能保证数据的原子性。synchronized 关键字两者都能保证。
12.2 如何禁止指令重排序
在 Java 中volatile 关键字除了可以保证变量的可见性还有一个重要的作用就是防止 JVM 的指令重排序。 如果我们将变量声明为 volatile 在对这个变量进行读写操作的时候会通过插入特定的 内存屏障 的方式来禁止指令重排序。
12. 关键字synchronized的三个使用场景
修饰实例方法修饰静态方法修饰代码块
12.1 修饰实例方法
锁当前对象实例
给当前对象实例加锁进入同步代码前要获得 当前对象实例的锁 。
synchronized void method() {//bussiness code
}12.2 修饰静态方法
给当前类加锁会作用于类的所有对象实例 进入同步代码前要获得 当前 class 的锁。
这是因为静态成员不属于任何一个实例对象归整个类所有不依赖于类的特定实例被类的所有实例共享。
synchronized static void method() {//business code
} 12.3 修饰代码块
锁指定对象或者指定类 对括号里指定的对象/类加锁
synchronized(object) 表示进入同步代码库前要获得 给定对象的。synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁。
synchronized(this) {//bussiness code
}12.4 总结
synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁synchronized 关键字加到实例方法上是给对象实例上锁尽量不要使用 synchronized(String a) 因为 JVM 中字符串常量池具有缓存功能。
12.5 静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥么
不互斥如果一个线程 A 调用一个实例对象的非静态 synchronized 方法而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法是允许的不会发生互斥现象因为访问静态 synchronized 方法占用的锁是当前类的锁而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
13.synchronized 和 volatile 有什么区别
synchronized 关键字和 volatile 关键字是两个互补的存在而不是对立的存在
volatile 关键字是线程同步的轻量级实现所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。volatile 关键字能保证数据的可见性但不能保证数据的原子性。synchronized 关键字两者都能保证。volatile关键字主要用于解决变量在多个线程之间的可见性而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
14. ReentrantLock是什么
ReentrantLock 实现了 Lock 接口是一个可重入且独占式的锁和 synchronized 关键字类似。不过ReentrantLock 更灵活、更强大增加了轮询、超时、中断、公平锁和非公平锁等高级功能。
public class ReentrantLock implements Lock, java.io.Serializable {}ReentrantLock 里面有一个内部类 SyncSync 继承 AQSAbstractQueuedSynchronizer添加锁和释放锁的大部分操作实际上都是在 Sync 中实现的。Sync 有公平锁 FairSync 和非公平锁 NonfairSync 两个子类
14.1 公平锁和非公平锁的区别是什么
公平锁 : 锁被释放之后先申请的线程先得到锁。性能较差一些因为公平锁为了保证时间上的绝对顺序上下文切换更频繁。非公平锁锁被释放之后后申请的线程可能会先获取到锁是随机或者按照其他优先级排序的。性能更好但可能会导致某些线程永远无法获取到锁。
15.ThreadLocal 内存泄露问题是怎么导致的?
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用而 value 是强引用。所以如果 ThreadLocal 没有被外部强引用的情况下在垃圾回收的时候key 会被清理掉而 value 不会被清理掉。这样一来ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话value 永远无法被 GC 回收这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况在调用 set()、get()、remove() 方法的时候会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法。
16.线程池
16.1 使用线程池的好处
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。
16.2 如何创建线程池
16.2.1 通过ThreadPoolExecutor构造函数来创建推荐 16.2.2 通过 Executor 框架的工具类 Executors 来创建
我们可以创建多种类型的 ThreadPoolExecutor
FixedThreadPool该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时线程池中若有空闲线程则立即执行。若没有则新的任务会被暂存在一个任务队列中待有线程空闲时便处理在任务队列中的任务。SingleThreadExecutor 该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池任务会被保存在一个任务队列中待线程空闲按先入先出的顺序执行队列中的任务。CachedThreadPool 该方法返回一个可根据实际情况调整线程数量的线程池。初始大小为 0。当有新任务提交时如果当前线程池中没有线程可用它会创建一个新的线程来处理该任务。如果在一段时间内默认为 60 秒没有新任务提交核心线程会超时并被销毁从而缩小线程池的大小。ScheduledThreadPool该方法返回一个用来在给定的延迟后运行任务或者定期执行任务的线程池。
16.3 为什么不推荐使用内置线程池
容易造成OOM
FixedThreadPool 和 SingleThreadExecutor使用的是无界的 LinkedBlockingQueue任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求从而导致 OOM。CachedThreadPool使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE 如果任务数量过多且执行速度较慢可能会创建大量的线程从而导致 OOM。ScheduledThreadPool 和 SingleThreadScheduledExecutor : 使用的无界的延迟阻塞队列DelayedWorkQueue任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求从而导致 OOM。
16.4 线程池常见参数有哪些 /*** 用给定的初始参数创建一个新的ThreadPoolExecutor。*/public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量int maximumPoolSize,//线程池的最大线程数long keepAliveTime,//当线程数大于核心线程数时多余的空闲线程存活的最长时间TimeUnit unit,//时间单位BlockingQueueRunnable workQueue,//任务队列用来储存等待执行任务的队列ThreadFactory threadFactory,//线程工厂用来创建线程一般默认即可RejectedExecutionHandler handler//拒绝策略当提交的任务过多而不能及时处理时我们可以定制策略来处理任务) {if (corePoolSize 0 ||maximumPoolSize 0 ||maximumPoolSize corePoolSize ||keepAliveTime 0)throw new IllegalArgumentException();if (workQueue null || threadFactory null || handler null)throw new NullPointerException();this.corePoolSize corePoolSize;this.maximumPoolSize maximumPoolSize;this.workQueue workQueue;this.keepAliveTime unit.toNanos(keepAliveTime);this.threadFactory threadFactory;this.handler handler;}
ThreadPoolExecutor 3 个最重要的参数
corePoolSize : 任务队列未达到队列容量时最大可以同时运行的线程数量。maximumPoolSize : 任务队列中存放的任务达到队列容量的时候当前可以同时运行的线程数量变为最大线程数。workQueue: 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数如果达到的话新任务就会被存放在队列中。
ThreadPoolExecutor其他常见参数 :
keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候如果这时没有新的任务提交多余的空闲线程不会立即销毁而是会等待直到等待的时间超过了 keepAliveTime才会被回收销毁线程池回收线程时会对核心线程和非核心线程一视同仁直到线程池中线程的数量等于 corePoolSize 回收过程才会停止。unit : keepAliveTime 参数的时间单位。threadFactory :executor 创建新线程的时候会用到。handler : 拒绝策略即当线程池满后新进入线程如何处理的策略。
16.4 线程池4中拒绝策略 ThreadPoolExecutor.AbortPolicy 抛出 RejectedExecutionException来拒绝新任务的处理。 ThreadPoolExecutor.CallerRunsPolicy 调用执行自己的线程运行任务也就是直接在调用execute方法的线程中运行(run)被拒绝的任务如果执行程序已关闭则会丢弃该任务。因此这种策略会降低对于新任务提交速度影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话你可以选择这个策略。 ThreadPoolExecutor.DiscardPolicy 不处理新任务直接丢弃掉。 ThreadPoolExecutor.DiscardOldestPolicy 此策略将丢弃最早的未处理的任务请求。
16.5 线程池阻塞队列有哪些
不同的线程池会选用不同的阻塞队列我们可以结合内置线程池来分析。 容量为 Integer.MAX_VALUE 的 LinkedBlockingQueue无界队列FixedThreadPool 和 SingleThreadExector 。FixedThreadPool最多只能创建核心线程数的线程核心线程数和最大线程数相等SingleThreadExector只能创建一个线程核心线程数和最大线程数都是 1二者的任务队列永远不会被放满。 SynchronousQueue同步队列CachedThreadPool 。SynchronousQueue 没有容量不存储元素目的是保证对于提交的任务如果有空闲线程则使用空闲线程来处理否则新建一个线程来处理任务。也就是说CachedThreadPool 的最大线程数是 Integer.MAX_VALUE 可以理解为线程数是可以无限扩展的可能会创建大量线程从而导致 OOM。 DelayedWorkQueue延迟阻塞队列ScheduledThreadPool 和 SingleThreadScheduledExecutor 。DelayedWorkQueue 的内部元素并不是按照放入的时间排序而是会按照延迟的时间长短对任务进行排序内部采用的是“堆”的数据结构可以保证每次出队的任务都是当前队列中执行时间最靠前的。DelayedWorkQueue 添加元素满了之后会自动扩容原来容量的 1/2即永远不会阻塞最大扩容可达 Integer.MAX_VALUE所以最多只能创建核心线程数的线程。
17.AOT 有什么优点为什么不全部使用 AOT 呢
JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation) 。和 JIT 不同的是这种编译模式会在程序被执行前就将其编译成机器码属于静态编译C、 CRustGo 等语言就是静态编译。AOT 避免了 JIT 预热等各方面的开销可以提高 Java 程序的启动速度避免预热时间长。并且AOT 还能减少内存占用和增强 Java 程序的安全性AOT 编译后的代码不容易被反编译和修改特别适合云原生场景。
只能说 AOT 更适合当下的云原生场景对微服务架构的支持也比较友好。除此之外AOT 编译无法支持 Java 的一些动态特性如反射、动态代理、动态加载、JNIJava Native Interface等。然而很多框架和库如 Spring、CGLIB都用到了这些特性。如果只使用 AOT 编译那就没办法使用这些框架和库了或者说需要针对性地去做适配和优化。举个例子CGLIB 动态代理使用的是 ASM 技术而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是 .class 文件如果全部使用 AOT 提前编译也就不能使用 ASM 技术了。为了支持类似的动态特性所以选择使用 JIT 即时编译器。