java高频面试合集

使用设计模式,实现代码的重构,提高代码的复用性 、扩展性,减少代码冗余问题

一、Redis与mysql如何保证数据性一致?(数据有改变)

  1. 传统的解决方案:直接清理redis缓存,清理缓存这个过程存在时间差

  2. 使用阿里巴巴Canal+kafka或基于MQ解决

二、消息中间件高并发情况下,如何保证消息不丢失?

1、如果消费者没有启动,消息中间件(MQ)中的数据不会丢失,默认缓存。

2、消息中间件如果宕机呢?消息也不会丢失,默认持久化。

3、如果消息中间件存满的情况下?,达到容量阈值,消息中间件就会通过拒绝策略来拒绝接收消息。

4、如果生产者投递消息时,消息中间件突然宕机?消息中间件存在确认机制,确认成功了才存放到消息中间件。

5、如果消费失败的情况下?手动实现ACK,失败的信息存放到另一个队列中,再通过正确的消费机制获取数据,确保消息机制消费成功后,才会将消息删除

三、HashMap与HashTable的区别?

1 HashMap是非同步的,没有对读写等操作进行锁保护,所以是线程不安全的,在多线程场景下会出现数据不一致的问题。

HashTable是同步的,所有的读写等操作都进行了锁(synchronized)保护,在多线程环境下没有安全问题。但是锁保护也是有代价的,会对读写的效率产生较大影响。

2 HashMap结构中,是允许保存null的,Entry.keyEntry.value均可以为null。但是HashTable中是不允许保存null的。

3 HashMap的迭代器(Iterator)是fail-fast迭代器,但是Hashtable的迭代器(enumerator)不是fail-fast的。如果有其它线程对HashMap进行的添加/删除元素,将会抛出ConcurrentModificationException,但迭代器本身的remove方法移除元素则不会抛出异常。这条同样也是Enumeration和Iterator的区别。

 

四、String StringBufferStringBuilder区别

 

三者共同之处:都是final,不允许被继承,主要是从性能和安全性上考虑的,因为这几个类都是经常被使用着,且考虑到防止其中的参数被参数修改影响到其他的应用。

 

StringBuffer是线程安全,可以不需要额外的同步用于多线程中;

 

StringBuilder是非同步,运行于多线程中就需要使用着单独同步处理,但是速度就比StringBuffer快多了;

 

StringBufferStringBuilder两者共同之处:可以通过appendindert进行字符串的操作。

 

String实现了三个接口:SerializableComparable<String>CarSequence

 

StringBuilder只实现了两个接口SerializableCharSequence,相比之下String的实例可以通过compareTo方法进行比较,其他两个不可以。

 

 

 

五、Finalstaticsynchronized等关键字

 

关键字的定义和特点:

 

        定义:被JAVA预言赋予了特殊含义,用做专门用途的字符串(单词)

 

        特点:关键字中所有字母都为小写字母

 

官方地址:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html

 

 

 

1用于定义类,函数,变量修饰符的关键字:

 

abstract final static synchronized

 

Final修饰的算常量,static修饰的是静态的,可以直接用类名调用。

 

(1)final变量:final变量经常和static关键字一起使用,作为常量

 

static修饰变量,只会被初始化一次在类加载时就会被初始化,不会因为对象的创建再次被加载final修饰基本数据类型变量时,必须赋予初始值且不能被改变修饰引用变量new Textdata()时,该引用变量不能再指向其他对象但是对象里面的内容可以改变

 

(2) final方法:不可以被子类的方法重写。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。

 

(3) final类final类通常功能是完整的,不能被继承。Java中有许多类是final的,譬如String, IntergerStringBuffer,StringBuilder以及其他包装类

 

 

 

 

 

 

 

 

 

 2用于定义建立实例及引用实例,判断实例的关键字:

 

New  this    super   instanceof

 

共同点:super()this()都可以代表一个临时对象执行构造函数,并且都要放在构造函数的第一行代表对象(父类对象,当前类对象)

 

不同点:super一般用在子类中,用于代表父类的对象;this用于当前类中,用于代表正在执行该代码的对象。

 

 

 

 

 

3其他修饰符关键字:

 

native strictfp transient   volatile   assert

 

 transient关键字只能修饰变量,而不能修饰方法和类。变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

 

 

 

4Volatile、synchronized 并发编程关键字

 

 

 

 

 

 

 

六、ArrayList vector等的区别

 

 

 

七、java内存分配知识

 

Java的内存分配上,主要分4个块!

 

一块是用来装代码的,就是编译的东西。

 

一块是用来装静态变量的,例如用static关键字的变量。

 

一块是stack,也就是RAM内存),存放基本数据类型和引用类型的!但区别在于,装了变量以后,变量上是有值的,而引用类型本身在stack上是没有值的是一个指针

 

一块是heap,也就是堆RAM内存),用于存放对象(实体)可以一句话概括,装new出来的东西!

 

一块是常量池,(实际上是存放于堆中的一块内存中);

 

所以综上所述,基本数据类型都在stack中,而引用类型,变量是放在stack中,真正有内容的东西放在heap中,也就是当new了一个新的引用类型,他就会放在堆中,同时栈中的引用类型变量会指向堆中你new出来的东西

 

相同之处:与堆存在RAM中。且java会自动管理堆和栈,堆是由Java虚拟机的自动垃圾回收器来管理回收时间不固定),栈会被自动回收(周期是固定的,程序运行完成就被回收)

 

区别之处:存储数据类型、灵活性(堆更灵活,自动分配不必了解任何信息)、存储速度(栈更快,二进制内存小,且数据大小和生命周期固定)

 

这样说希望能帮你了解到java的内存分配问题。

 

基本数据类型有:byte:Java中最小的数据类型;short:短整型;int:整型;long:长整型;float:浮点型;double:双精度浮点型;char:字符型;boolean:布尔类型。

 

引用数据类型有:类String、Integer等...、接口类型、数组类型、枚举类型、注解类型。

 

 

 

八、Jdkjre有什么区别

 

 

 

 

 

 

 

 

 

九、多线程的实现方式和方法

 

 

 

 

 

框架

 

Spring

 

 

 

数据库

 

 

分布式

 

 

 

 

 

有关java的一般问题

1、什么是JVM(Java Virtual Machine)?为什么称Java为跨平台的编程语言?

Java虚拟机(JVM)是可以执行Java字节码的虚拟机,每个Java源文件将被编译成字节码文件,然后在JVM中执行,保证跨平台运行

2. JDK(Java Development Kit)和JRE(Java Runtime Enviroment)之间的差异是什么?

Java运行环境(JRE) 是主要运行基本的Java虚拟机(JVM)的环境。Java开发环境(JDK),编译和执行Java应用程序;软件开发包,包含JRE,编译器和工具。

3. “static” 关键字是什么意思?在Java里可以 override private 或 static 的方法吗? 

static:访问这个成员变量或方法时,不必获取它的类实例

Java 里的 static 方法不能被 override,因为 override 的机制是运行时(runtime)的动态绑定,而 static 方法是在编译时静态绑定的。

4. 在静态方法里可以访问非静态变量吗?

Java 中的 static 变量归相应的类所有,它的值对于类的所有实例都是相同的。static 变量是在 JVM 加载类的时候初始化的。如果代码试图访问非静态的变量,而且不是通过类的实例去访问,编译器会报错,因为这些非静态变量还没有被创建呢,并且它们没有与实例相关联。

5. Java 支持哪些数据类型?什么是 Autoboxing 和 Unboxing?

Java中有八种基本数据类型以及引用类型

基本数据类型:byte(1字节),short(2字节),int(4字节),long(8字节),char(2字节),boolean(不确定,取值是true/false),float(4字节),double(8字节)

引用数据类型:包括数组,集合,字符串,接口以及类等

自动装箱/自动拆箱(Autoboxing 和 Unboxing):就是指基本数据类型可以和其对应包装类之间自动转换

Autoboxing 是指在基本数据类型和对应的包装(wrapper)类之间Java 编译器所做的自动转换。例如,编译器将 int 转换为 Integer,将 double 转换为 Double ,等等。逆向的转换称为 unboxing。

Java 线程

 

11. 进程与线程的区别 ?
进程是一个程序的执行(即正在运行的程序), 然而线程是在进程中独立的执行序列. 一个进程可以包含很多线程. 线程有时被称为轻量级的进程.

12. 说下创建线程的不同方式. 你倾向于哪种方式并说明原因 ?
有三种创建线程的方式:详细https://www.cnblogs.com/songshu120/p/7966314.html

  • 继承Thread类.
  • 实现Runnable接口.
  • 通过Executor框架创建线程池.

首选方式是实现Runnable接口, 因为它不需要继承Thread类. 当你的程序设计需要多继承时, 使用接口会有所帮助. 另外, 线程池效率是很高的, 并且实施起来也很简单.

13. 解释下可用的线程状态.

1.新建(new):新创建了一个线程对象。2.可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权。3.运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice ),执行程序代码。4.阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有    机会再次获得cpu timeslice 转到运行(running)状态。

阻塞的情况分三种:

等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join ()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
 5.死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。


14. 同步方法与同步块的区别 ?

为何使用同步?

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(增删改查),将会导致数据的不准确,相互之间产生冲突。

类似于在atm取钱,银行数据确没有变,这是不行的,要存在于一个事务中。因此加入了同步锁,以避免在该线程没有结束前,调用其他线程。从而保证了变量的唯一性,准确性。
————————————————

区别?

同步是高开销的操作,因此尽量减少同步的内容。通常没有必要同步整个方法,同步部分代码块即可

同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;

1.同步方法:
用关键字 synchronized修饰方法
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用给方法前,要获取内置锁,否则处于阻塞状态。
例:public synchronized getMoney(){}
注:synchronized修饰静态方法,如果调用该静态方法,将锁住整个类。

2.同步代码块
即有synchronized修饰符修饰的语句块,被该关键词修饰的语句块,将加上内置锁。实现同步。
例:synchronized(Object o ){

    代码内容

  }

详细:https://www.cnblogs.com/woyaobianfei/p/8046616.html


15. 在监视器中的线程同步是怎样发生的? 你可以使用哪些级别的同步 ?

监视器和锁(即线程同步)在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。线程在获取锁之前不允许执行同步代码。在 java 虚拟机中, 为了实现监视器的互斥功能, 每个对象都关联着一把锁。一旦方法或者代码块被 synchronized 修饰, 那么这个部分就放入了监视器的监视区域, 确保一次只能有一个线程执行该部分的代码, 线程在获取锁之前不允许执行该部分的代码 ;

另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案


16. 什么是线程锁机制?

什么是线程锁机制:

  多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!so,不使用线程锁, 可能导致错误

  优点:保证资源同步 
  缺点:有等待肯定会慢

锁优化建议

减少锁的时间

不需要同步执行的代码,能不放在同步快里面执行就不要放在同步快内,可以让锁尽快释放。

减少锁的粒度

它的思想是将物理上的一个锁,拆成逻辑上的多个锁,增加并行度,从而降低锁竞争。它的思想也是用空间来换时间。

锁粗化

大部分情况下我们是要让锁的粒度最小化,锁的粗化则是要增大锁的粒度。
在以下场景下需要粗化锁的粒度: 
假如有一个循环,循环内的操作需要加锁,我们应该把锁放到循环外面,否则每次进出循环,都进出一次临界区,效率是非常差的。

使用读写锁

ReentrantReadWriteLock 是一个读写锁,读操作加读锁,可以并发读,写操作使用写锁,只能单线程写。

读写分离

CopyOnWriteArrayList 、CopyOnWriteArraySet 
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。 
 CopyOnWrite并发容器用于读多写少的并发场景,因为,读的时候没有锁,但是对其进行更改的时候是会加锁的,否则会导致多个线程同时复制出多个副本,各自修改各自的。

使用CAS

如果需要同步的操作执行速度非常快,并且线程竞争并不激烈,这时候使用CAS效率会更高,因为加锁会导致线程的上下文切换,如果上下文切换的耗时比同步操作本身更耗时,且线程对资源的竞争不激烈,使用volatiled+cas操作会是非常高效的选择;

消除缓存行的伪共享

除了我们在代码中使用的同步锁和jvm自己内置的同步锁外,还有一种隐藏的锁就是缓存行,它也被称为性能杀手。 
在多核cup的处理器中,每个cup都有自己独占的一级缓存、二级缓存,甚至还有一个共享的三级缓存,为了提高性能,cpu读写数据是以缓存行为最小单元读写的;32位的cpu缓存行为32字节,64位cup的缓存行为64字节,这就导致了一些问题。 
例如,多个不需要同步的变量因为存储在连续的32字节或64字节里面,当需要其中的一个变量时,就将它们作为一个缓存行一起加载到某个cup-1私有的缓存中(虽然只需要一个变量,但是cpu读取会以缓存行为最小单位,将其相邻的变量一起读入),被读入cpu缓存的变量相当于是对主内存变量的一个拷贝,也相当于变相的将在同一个缓存行中的几个变量加了一把锁,这个缓存行中任何一个变量发生了变化,当cup-2需要读取这个缓存行时,就需要先将cup-1中被改变了的整个缓存行更新回主存(即使其它变量没有更改),然后cup-2才能够读取,而cup-2可能需要更改这个缓存行的变量与cpu-1已经更改的缓存行中的变量是不一样的,所以这相当于给几个毫不相关的变量加了一把同步锁; 
为了防止伪共享,不同jdk版本实现方式是不一样的: 
1. 在jdk1.7之前会 将需要独占缓存行的变量前后添加一组long类型的变量,依靠这些无意义的数组的填充做到一个变量自己独占一个缓存行; 
2. 在jdk1.7因为jvm会将这些没有用到的变量优化掉,所以采用继承一个声明了好多long变量的类的方式来实现; 
3. 在jdk1.8中通过添加sun.misc.Contended注解来解决这个问题,若要使该注解有效必须在jvm中添加以下参数: 
-XX:-RestrictContended

sun.misc.Contended注解会在变量前面添加128字节的padding将当前变量与其他变量进行隔离; 

Volatile与Synchronized的区别

1、volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;并且volatile只能修饰变量,而synchronized可以修饰方法,以及代码块。

2、从内存可见性角度看,volatile读相当于加锁,volatile写相当于解锁。

3、synchronized既能保证可见性和有序性,又能保证原子性(更安全),而volatile只能保证可见性,无法保证原子性。

4、关键字volatile解决的是变量在多个线程之间的可见性问题,而synchronized关键字解决的是多个线程之间访问资源的同步性。

sleep和wait的区别

请看:https://segmentfault.com/a/1190000011487582

1、sleep是Thread的静态方法、wait是Object的方法;
2、sleep不释放锁对象,wait放弃锁对象;
3、sleep暂停线程,但监控状态仍然保持,结束后自动恢复;
4、wait、notify和notifyAll只能在同步控制方法控制块里面使用,而sleep可以在任意地方使用;
5、wait方法导致线程放弃对象锁,只有针对此对象发出notify(或notifyAll)后才进入对象锁定池准备重新获得对象锁,然后进入就绪状态,准备运行。

sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。
注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的,它只会是使当前线程被sleep 而不是t线程。

wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()或notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生InterruptedException异常。

其实两者都可以让线程暂停一段时间,但是本质的区别是sleep是线程的运行状态控制,wait是线程之间的通讯问题。sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定。好比如说,我要做的事情是"点火->烧水->煮面",而当我点完火之后我不立即烧水,我要休息一段时间再烧。对于运行的主动权是由我的流程来控制。而wait(),首先,这是由某个确定的对象来调用的,将这个对象理解成一个传话的人,当这个人在某个线程里面说"暂停!",也是 thisOBJ.wait(),这里的暂停是阻塞。还是"点火->烧水->煮饭",thisOBJ就好比一个监督我的人站在我旁边,本来该线程应该执行1后执行2,再执行3,而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但正个流程并没有结束,我一直想去煮饭,但还没被允许,直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisOBJ.notify()的时候,那么我就可以煮饭了,这个被暂停的线程就会从暂停处继续执行。

16. 什么是死锁?

详细:https://www.cnblogs.com/Kevin-ZhangCG/p/9038223.html

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

  例如,在某个计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

死锁检测和解除
先检测:及时地检测出死锁的发生,并确定与死锁有关的进程和资源。检测方法包括定时检测、效率低时检测、进程等待时检测等。
然后解除死锁:采取适当措施,从系统中将已发生的死锁清除掉。
这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。

如果我们在死锁检查时发现了死锁情况,那么就要努力消除死锁,使系统从死锁状态中恢复过来。消除死锁的几种方式:

1、最简单、最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程;

2、撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素;

3、进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。

其实即便是商业产品,依然会有很多死锁情况的发生,例如 MySQL 数据库,它也经常容易出现死锁案例。

17. 怎样确保N个线程访问N个资源时不会发生死锁 ?

答:使用多线程时,一种非常简单的避免死锁的方式就是:指定锁的顺序,并强制线程按照指定的顺序获取锁。因此所有的线程都是以同样的加锁和释放锁,就不会出现死锁了

下面是详细解释:

预防死锁,预先破坏产生死锁的四个条件。互斥不可能破坏,所以有如下3种方法:

1.破坏  请求和保持条件

1.1)进程等所有要请求的资源都空闲时才能申请资源,这种方法会使资源严重浪费(有些资源可能仅在运行初期或结束时才使用,甚至根本不使用)

1.2)允许进程获取初期所需资源后,便开始运行,运行过程中再逐步释放自己占有的资源。比如有一个进程的任务是把数据复制到磁盘中再打印,前期只需要获得磁盘资源而不需要获得打印机资源,待复制完毕后再释放掉磁盘资源。这种方法比上一种好,会使资源利用率上升。

2.破坏  不可抢占条件

这种方法代价大,实现复杂

3.破坏 循坏等待条件

对各进程请求资源的顺序做一个规定,避免相互等待。这种方法对资源的利用率比前两种都高,但是前期要为设备指定序号,新设备加入会有一个问题,其次对用户编程也有限制

原文地址:https://www.cnblogs.com/huoxiansudi/p/11910243.html