Java基础部分高频面试题总结

1.说一下面向对象的三大特征,有什么特点?

继承、封装、多态
继承:继承就是子类通过extends继承父类的方法,是发生在类与类之间的,可以复用父类的方法,同时也可以在这个基础上对方法进行重写来满足我们的业务需求,需要注意的是,子类不能继承父类的构造方法。
封装:封装就是通过private把对象的属性和实现细节给隐藏起来,同时给外界提供一个公共的入口来进行访问;可以提高信息的安全和降低代码的耦合性。
多态:多态指的是我们平时定义的引用变量所指向的具体类型在编程时是不确定的,是在我们程序运行的阶段才能知道具体指向了哪一个实例对象;多态可以降低耦合性,使代码更加灵活多变;多态存在有三个必要条件:继承、重写、父类的对象指向子类的引用。

补充:

多态的实现方式?

1.方法重写或者重载2.接口实现3.抽象类和抽象方法

重写和重载的区别?

重写发生在子类和父类之间,是当父类的方法不能满足需求子类需求的时候来重写方法。重载发生在一个类中,方法名相同,形参列表不同。

构造器是否可以被重写?

构造不能被重写,但是可以被重载

重载的方法是否可以根据返回值类型进行区分?

不可以,因为调用的时候不能指定类型信息,编译器不知道你调用的是哪个方法。

2.String为什么要用final修饰?

首先我们知道被final修饰的类被称为最终类,是不能被重写的,而String作为一个我们的常用类,在设计的时候底层调动了许多我们计算机本地的一些方法去进行操作,如果我们不用final修饰,就可以重写这些方法,那么在安全方面会存在很大的问题;其次被final修饰后也是不可变的,所以在创建的时候,他的hashcode就被缓存了,字符串的处理速度比较快,所以我们在map中也经常使用字符串作为键。

String的常用方法?

我们在平时工作中常用的方法的话大概有:字符串比较equal、大小写转换touppercase/tolowercase、去除空白tirim、分割split、替换replace等等。

==和equals的区别

== 对于基本数据类型比较的是值,对于引用数据类型比较的是地址值
equals重写后比较的是引用类型对象的地址值

为什么重写equal后,hashcode也必须重写?

hashcode在具有哈希机制的集合中非常重要,比如说我们常用的hashset或者hashmap,在向hashset中添加对象时,首先会计算hashcode值来确定对象的储存位置,当这个位置没有对象时,会直接添加到这个位置,如果这个位置有对象,那么会判断他们equals比较的结果,如果还会相等,那么添加对象失败,如果不同的话,会把对象添加到其他位置;所以,假如我们不重写hashcode,那么就会导致每一个对象的hashcode值都一定不相等,所以当添加对象的时候都会成功,这样的话会出现大量的重复key,在集合中key的值会唯一,所以我们重写了equals后必须重写hashcode;同时我们在添加对象的时候是先比较的hashcode的值,如果hashcode值不同,他们一定不想等,可以在一定程度上提高集合的效率。 (总结:第一,在HashSet等集合中,不重写hashCode方法会导致其功能出现问题;第二,可以提高集合效率。)

String Stringbuilder StringBuffer的区别

技巧:是否可变,是否安全,效率高低
他们都是用来操作字符串的类
String类有final修饰,是不可变的,也是安全的,我们每次对它进行修改的时候都会重新创建一个新的对象,所以在我们需要经常对字符串进行操作的时候不适合用String类。
StringBuffer是可变的,他们可以主要是通过调用append和insert方法对字符串进行操作,同时它也被synconaized修饰,所以是线程安全的。适合在多线程的时候使用。
StringBuider基本和Buffer一致,区别是,它没有synconaized修饰,所以是线程不安全的,但效率比Buffer高,所以在不考虑线程安全的问题时可以优先选用StringBuilder.

3.说一下java的四大权限修饰符有哪些

public 默认不写 protect private
public的话是对所有类都可以用
默认不写 是默认权限 对本类和同一个包中的类都可见
protect 是受保护的权限,只能是本类和它的子类可以使用(子类可以不同包)
private 私有的权限,只能在本类中使用。在项目中的话我们用的比较多的是pubic和private

4.static关键字的作用

技巧:修饰变量 + 修饰方法+ 修饰代码块
被static修饰的变量称为静态变量,在类初次加载的时候就会被加载,内存中只有一个副本,被所有对象共享,我们可以直接通过类名来直接访问。
被static修饰的方法称为静态方法,也是在类初次加载的时候就会被加载,我们可以直接通过类型.方法名的方式来访问,但需要注意的是我们可以在非静态方法中访问静态方法却不能在静态方法中去访问非静态方法(可能会问原因?)
被static修饰的代码块称为静态代码块,我们通常会把一些需要提前加载或者只需要进行一次初始化操作的代码放到静态代码块中,这样可以跟随类加载的时候一起加载,优化程序的性能。

5.final关键字的作用

技巧:修饰变量+修饰方法+修饰类
final修饰变量 被修饰的变量不可以改变
final修饰方法 不能被重写
final修饰类 不能被重写

final finally finalize区别

final 可以修饰变量 方法 类
finally 一般是用在try catch代码块中,在处理异常的时候,放在finally中的代码不管是否出现异常都会被执行,一般用来存放关闭静态资源的代码
finalize 是obeject类中的一个方法,通常用在垃圾回收的时候,当我们调用system.gc方法的时候,垃圾回收器回去调用finalize()方法回收垃圾。

6.this和supper的区别

技巧:this用于当前类 supper指向父类
相同点:
都必须放在构造方法的第一行调用、否则会报错;this和super都指向的是对象,所以不能在static环境下使用
不同点:
this:一般用来引用当前类的变量、方法和构造函数,主要是在同一类的不同构造函数中使用。
supper:用来引用父类的变量、方法和构造函数,主要是对父类构造方法的调用,用在子类中。

7.抽象类和接口的区别

技巧:相同点,不同点(定义,修饰符,实现,继承,字段声明,构造器)
相同点:
都不可以被实例化,子类都必须重写父类的抽象方法
不同点
定义:抽象类用abstract 定义 接口用interface
修饰符:抽象类可以用public protect 默认不写, 接口只能用public修饰
实现:抽象是用extends继承的方式 接口是用的impements实现的方式
继承:单继承,多实现
字段声明:抽象类字段声明可以是任意的;接口的字段默认是由static和final修饰,可以用来定义全局常量
构造器:抽象类有构造器 接口没有

8.jdk1.8提供了哪些新特性

1.lambda表达式:本质就是一个匿名内部类的简写
2.stream流:在新的stream包中针对集合的操作提供了并行和串行操作流
3.函数式接口:接口里面只有一个抽象方法
4.接口新增了默认方法和静态方法
5.新的日期API:相对于以前老的API,线程安全,有专门的时区处理

stream流常用API:

由于是针对集合的操作,有遍历foreach(),排序sorted(),去重distinct(),最大/最小max()/min()

9.临时补充:(重点)

SpringBoot的自动配置原理?
SpringBoot启动类上有一个重要的注解@SpringBootApplication,这是一个组合注解,启动的时候,可以通过它里面的@EnableConfiguration注解去找到META-INF里面的所有自动配置类,并进行加载,而这些配置类都是以AutoConfiguration结尾来命名的,这个配置类能够通过以Properties结尾命名的类取得配置中的属性,当我们使用run()方法启动的时候就可以通过@AutoConfigurationImportSelector注解去筛选加载,完成SpringBoot的自动配置。

10.反射机制是什么?

技巧:动态获取信息和动态调用方法的功能
java的反射机制就是在运行的状态下,对于任意一个类,都可以获得它的属性和方法,对于任意一个对象,都可以去调用他的属性和方法,这种动态获取信息以及动态调用方法的功能就是java的反射机制。
优点:动态加载类,提高代码的灵活度。
缺点:性能比直接的java代码要慢很多。(它里面的Method/invoke方法会对参数进行封装和解封的操作,同时也会对参数进行一个校验)
应用场景:(这两个回答的相关问题需要去了解,防止面试官问到
1.java中的很多框架都用到了反射,比如spring中xml的配置
2.动态代理设计模式也用到了反射机制

Java获取Class对象的三种方式

public class Main{
  public static void main(String[] args) throws ClassNotFoundException {
  //方式1
   Person p1 = new Person();
   Class c1 = p1.getClass();
   //方式2
  Class c2 = Person.class;
  //方式3可能会抛出ClassNotFoundException异常
  Class c3 = Class.forName("com.company");
  }
}

11.java的异常有哪些?

java的异常有两个重要的子类,error和exception,error是JVM本身的错误,所以我们关注的是Exception,exception又分为运行时异常RuntimeException和非运行时异常,我们比较常见的运行时异常的话有
算数异常,数组越界异常,空指针异常,类型转换异常等等。

12.java容器相关面试题

Java 容器都有哪些?

数组,String,以及java.util包下的list set map,list常用的是arrayList和linkedList,set常用的是hashset,map常用的是hashmap

list set map的区别?

首先他们三个都是接口,都不可以被实例化。
list和set都是collection接口下的子接口,list是有序可重复的,set是无序不可重复的,map存放是key-value的键值对。

collection和colletions的区别?

collection是一个集合接口,也是list和set的父接口。colletctions是一个包装类,也可以说更像是一个工具类,它里面提供了许多对集合进行操作的静态方法,比如排序sort,复制copy,反转,替换,搜索等方法。

ArrayList 和 LinkedList 的区别是什么?

ArrayList和Linkedlist都是List的实现类,同时也都是线程不安全的。
ArrayList是基于我们数组实现的,而LinkedList是基于链表,所以我们在需要进行查询的时候,更适合使用ArrayList,因为它实现了RandomAccess接口,所以我们可以快速定位到某个具体的元素;在增删的时候更适合我们的linkedList。这个跟数组和链表的结构有关,当我们使用ArrayList进行增删时,我们可能需要对数组进行扩容或者是复制的操作,这样效率肯定慢,而使用linkenList的话,由于是基于链表的,所以我们只用改变对应元素的两个节点的位置就可以完成操作,所以效率高。

ArrayList 和 Vector 的区别是什么?

技巧:安全,效率,扩容
ArrayList 和 Vector都是list的实现类,底层都是基于数组实现。
ArrayList是线程不安全的,Vector有Synchronized修饰,所以说是线程安全的。同时因为加了锁的原因,vector的效率相对于ArrayList也更低。在需要扩容的时候Arraylist是会扩容百分之50,而Vecto扩容会增加1倍。

HashMap 的实现原理?

HashMap是基于map实现的,以key-value的形式对数据进行存储,在jdk1.8以后,数据结构从数组+链表形式升级为数组+链表+红黑树的形式;当我们使用put方法存入一个元素的时候,hashmap会调用key.hashcode方法计算hashcode值,根据hash值将value保存在bucket里面,当hashcode的值相同的时候,会发生hash碰撞,hashmap解决hash碰撞的方式是使用 使用链表和红黑树,当冲突个数比较少的时候存到链表里面,冲突个数超过8个的时候会将链表转换成红黑树(treeifyBin方法())。

补充:如果两个key的hashcode相同,你如何获取值对象

首先调用get方法根据key.hashcode的值找到bucket中这个对象位置,如果有两个值对象,会对链表进行遍历,然后找到值对象。

这时会如果问:你并没有值对象去比较,你是如何确定确定找到值对象的?

首先找到在bucket的位置之后,会调用key.equals方法去找到链表中正确的节点,最终找到想要的对象。

Java中的HashMap的初始容量和扩容

hashmap是使用的懒加载的机制,在我们刚创建一个map对象的时候,他的初始容量是为0的,当我们调用map的put方法去进行添加操作的时候,这时put()方法里面才会调用resize()方法设置map的初始容量,默认的是设置为16,我们也可以自己指定容量。同时扩容的时机和负载因子有关,默认是0.75,也就是说当我们map的容量达到12的时候,会进行扩容,每次扩容的大小是2倍。

HashMap为什么会是2倍的形式进行扩容呢?

hashmap的初始容量都是2的n次方,我们扩容后的容量也是2的n次方,因为这样设计可以最大程度上减少hash碰撞,并且是通过位运算的方式,效率也比较高。并且所以在map进行扩容的时候是以2倍的方式进行扩容的。

HashMap 和 Hashtable 有什么区别?

HashMap 和 Hashtable 都是实现了map serializable 以及 可克隆 接口。
区别:
底层结构不同:HashMap底层数据结构1.8以前用的数组+链表的形式,1.8以后更新为数组+链表+红黑树;HashTable底层数据结构用的是数组+链表
初始容量不同:HashMap 初始容量是16,Hashtable 初始容量是11,负载因子都是0.75
添加键值对key-value的时候,使用的hash算法不同,HashMap 使用的是自定义的hash算法,hashTable使用的key.hashcode
HashMap允许key和value为null,HashTable不允许键或值为null
扩容机制不同:HashMap扩容的时候是扩容2倍,而Hashtab扩容的时候是2倍的基础上+1
HashMap只支持iterator遍历,HashTable支持itetator和enumeration遍历
HashMap是线程不安全的,Hashtable 是线程安全的。

JDK1.8前HashMap也是采用头插法,为什么1.8改为了尾插法?

1.8以前,hashmap的数据结构采用的是数组+链表的方式,使用头插法容易造成链表成环的问题,形成死链。

那为什么hashtable在1.8以后依然使用头插法?

因为hashtable是线程安全的,不用担心并发问题,使用头插法效率更高。

HashTable和ConcurrentHashMap的区别?

HashMap是线程不安全的,当出现多线程操作时,会出现安全隐患,我们可能会想到HashTable,是的,这个是线程安全的,但是HashTable用的是方法锁,把整个put方法都上锁了,这就导致了效率很低,如果把put方法比作是一个有很多房间的院子,那么HathTable的锁就相当于是把院子的大门锁上了。而ConcurrentHashMap是用的块锁,相当于是把院子里的有安全隐患的房间锁上了,这样一来,就不会让去其他房间办事的人等待了。
详细原理参考:https://blog.csdn.net/qq_45036591/article/details/105470901

Comparable和Comparator区别?

Comparable和 Comparator都是java.util包下的两个接口,都是用来做比较的。
Comparable接口用于进行自然排序,而Comparator接口用于自定义排序,自定义排序更加灵活方便而常用。
comparable在设计上不推荐使用,它对程序本身具有入侵性。

Iterator 怎么使用?有什么特点?

它可以用来遍历任何collection的接口,在使用的时候可以使用hasnext()方法进行循环,使用next()方法进行取值。它的特点就是更加安全,因为它的迭代器使用的是fast-fail机制,在当前遍历集合的元素被更改的时候就会抛出 并发修改异常(ConcurrentModificationExceptio)。

13.线程常见面试题汇总

一、线程基础

什么是线程?什么是进程?他们之间有什么区别?线程的好处和坏处?

进程是操作系统中分配资源的最小单元,而线程就是操作系统中调度资源的最小单元;通俗理解的话,我们电脑上启动的一个word应用程序就是一个线程,word程序里面我们可以同时打字和实现自动保存,这个就是线程完成的。
线程的好处就是可以解决部分同时运行的问题,比如我们可以一边听歌,一边打游戏;坏处的话就是我们开启的线程过多,每个线程都会占用资源,会造成我们的电脑卡顿。

Java中实现线程有哪几种方式?区别是什么?

三种方式:
1.通过继承Thread类,重写该类的run()方法 2.实现runable接口 3.实现callable接口
通过实现runable和callable接口的方式来实现线程的话,可以同时继承其他类,使得代码更具有灵活性,同时在这种方式下,多个线程可以同时共享一个对象,非常适合多个相同的线程来处理同一份资源的情况。访问当前线程要使用Thread.currentThread()方法;通过继承Thread类的方式来实现线程的话比较简单,可以直接使用this访问当前的线程.。

Thread类中的start和run方法的区别?(是否可以开启多线程)

通过start()方法来启动线程的时候,JVM处于一个就绪的状态,他会去调用run()方法来完成具体的业务逻辑,当run()方法结束以后此线程就会终止,可以达到多线程的目的。如果我们直接使用run()方法,此时它只会被当做一个普通方法来执行,不能达到多线程的目的。

守护线程和非守护线程的区别?(垃圾回收线程例子)

java里线程分为用户线程和守护线程,用户线程就是我们创建的普通线程,守护线程就是为了服务用户线程而存在的,比如我们的垃圾回收线程就是一个典型的守护线程;当我们的用户线程都结束了,只剩下了守护线程的时候,它没有了守护的对象,也就没有了存在的必要,所以会杀死所有的守护线程,终止程序。

为什么wait, notify 和 notifyAll这些方法不在thread类里面?

因为wait notify notifyAll都是锁级别的操作,而java提供的锁是对象级别的不是线程级别的,所以把他们定义在objiect类中。

Java中什么是竞态条件? 举个例子说明。

当两个线程去竞争同一个资源的时候,如果对访问资源的顺序敏感,那么就会出现竞态条件,产生竞态条件的区域的代码称为临界区,我们可以在临界区中使用适当的同步锁就可以解决竞态条件。

Java中如何停止一个线程?

在java中一般情况下我们是不需要去手动停止一个线程的,当run()方法里面的业务执行完成后它会自动停止,如果我们需要手动去停止一个线程有三种方法:
1.使用stop()方法,但是不推荐使用,因为这个是暴力停止线程,容易造成许多无法预料到的后果
2.使用flag标志,并使用volatile关键字进行修饰。
3.使用interrupt()方法,这个方法本身是不能直接中断线程的,我们需要去循环体里判断isInterrupt()方法是否为true来确定线程是否被终止
参考链接:https://blog.csdn.net/u014270696/article/details/107596468

interrupted 和 isInterrupted 方法的区别?

interrupt是用来中断线程,标记线程为中断状态
interrupted用来查询线程的中断状态,并清除原状态
isInterrupted也是用来查询线程的中断状态,但是不会清除原状态

sleep和wait的区别?

sleep和wait都是让线程暂停执行的方法。
sleep是Thread的方法,可以放在任何位置,wait是object类中的方法,只能放在同步方法或者同步代码块里面。
sleep不会释放 对象锁,也就说,在同步方法或者同步代码块中使用的时候,一个线程访问时,其他线程不能访问。wait的话是会释放 对象锁的。
在使用wait的时候,需要在另外一个线程中调用notify来进行唤醒,sleep不需要唤醒,他会等到休眠时间结束后自动接着执行。

如何在两个线程间共享数据?(根据多个线程的代码是否相同判断是否使用同一个Runnable对象)

如果每个线程的代码一样,可以使用同一个runnable对象,比如说我们的卖票系统
如果每个线程的代码不一样,可以使用不同的runnable对象,比如说我们的银行存取款

java的内存模型? (待解决~)

首先说出和内存结构的区别、然后是为什么需要内存模型(从计算机发展的角度来看逐步过渡到原子性、可见性、有序性),最后就是java内存模型如何解决上面三个问题;

有三个线程T1,T2,T3,怎么确保它们按顺序执行?

(多个方法,常见的join极其机制)
第一种方法:先执行最后一个线程T3,然后T3调用T2,T2调用T1
第二种方法:使用join()方法 join方法的主要作用就是同步, 可以是线程由并行变换为串行,相当于我在线程A中调用了B的join()方法,那么A线程会等待B线程执行完后才会执行;
参考:https://www.cnblogs.com/lcplcpjava/p/6896904.html

Thread类中的yield方法有什么作用?

作用是暂停当前正在执行的线程对象,让其它有相同优先级的线程执行,静态方法,只是可能性,不能保证确定性

线程的生命周期包括哪几个阶段?

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
新建:就是刚new出来的时候
就绪:刚执行start()方法后,线程处于等待被cpu分配资源的阶段
运行:线程获得CPU资源进入运行阶段
阻塞:当线程在执行的时候,因为一些原因变成了阻塞状态,比如使用sleep()或者wait()方法后。
销毁:当线程执行完任务,或者由于异常导致结束,或者我们强行终止任务的时候,线程需要被销毁。

二、线程池

为什么要使用线程池?

技巧:什么是线程池,为什么要用?
线程池是一种线程的使用模式,一个线程池中可以有多个线程,线程池就是创建若干个可执行的线程放在一个池里面,当我们有任务需要处理的时候,就会把任务提交到线程池中的任务队列里面,交给他进行处理,处理完以后线程不会被销毁,而是继续会等待下一次被调用。(举例:公交车例子)
线程池好处:假如说我们有50000个请求需要处理,并且每一个请求都需要单独为它创建一个线程,那么我们需要创建50000个线程来处理这些请求,一个线程的话的创建 执行 销毁都需要时间,那么如果我们使用线程池的话,那么可以大幅度减少线程的创建和销毁的时间,同时也可以对这些线程进行管理,提高工作的效率。

使用过哪些线程池?有什么使用场景?

newSingleThreadExcutor 它的特点是线程池中只有一个线程,每次执行一个任务
newFixedThreadPool它的特定是在线程中有固定线程数量,空闲线程会一直保留
newCachedThreadPool它的特点就是在有任务时才会创建线程,空闲线程保留60秒
newScheduledThreadPool它的特点是创建一个大小无限的线程池,线程池支持定时以及周期性执行任务的需求。

线程池的七个参数的作用

线程池中有几个重要的参数
threadFactory :线程工厂,用来创建线程
keepAliveTime :线程没有任务时最多保持多久时间终止
unit :keepAliveTime的时间单位
corePoolSize :核心线程数量
maximumPoolSize :线程最大线程数
workQueue :阻塞队列,存储等待执行的任务
rejectHandler :当拒绝处理任务时的策略

线程池的工作队列(workQueue )有哪些?

有一个基于数组结构的有界阻塞队列
有一个基于链表结构的阻塞队列
有一个不储存元素的阻塞队列
有一个具有优先级的无限阻塞队列

线程池的拒绝策略?

线程池通常会有四种拒绝策略:第一种是丢弃任务并抛出拒绝执行异常;第二种是丢弃任务,但是不抛出异常;第三种是丢弃队列最前面的任务,然后重新提交被拒绝的任务 ;第四种是由提交任务的线程处理当前这个任务。默认的话是使用的第一种丢弃任务并抛出拒绝执行异常的策略。

线程池的执行流程?

1.当有任务进来时,首先判断核心线程池里面是否有线程可以执行,有空闲线程的话,会创建线程执行任务
2.如果核心线程池没有线程可以执行任务了,会把任务丢到任务队列中
3.如果任务队列也已经满了,但运行线程小于最大的线程数量,那么会创建一个线程去执行任务,如果运行线程已经达到最大的线程数量的时候,那么此时就无法创建更多的线程去执行任务了,执行拒绝策略。

execute和submit的区别?

他们都是属于线程池的方法
executee只能提交Runnable类型的任务,会直接抛出任务执行时的异常,适用于不需要关注返回值的场景;
submit可以提交Runnable和Callable类型任务, 并且会吃掉异常,所以适用于只需要关注返回值的场景

三、锁系列

你能说简单说一下synchronize吗?

synchronize是java中的关键字,可以用来修饰局部变量、方法、类、代码块等;主要有三种作用:可以确保原子性、可见性、有序性,原子性就是能够保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等该线程处理完数据后才能进行;可见性就是当一个线程在修改共享数据时,其他线程能够看到,保证可见性,volatile关键字也有这个功能;有序性就是,被synchronize锁住后的线程相当于单线程,在单线程环境jvm的重排序是不会改变程序运行结果的,可以防止重排序对多线程的影响。‘

延伸一:java内存模型的三大特性,或者是说一下java内存模型,或者是synchronize跟java内存模型有什么关系吗?

1、什么是java内存模型:java虚拟机规范中定义了java内存模型是用来屏蔽各种硬件和操作系统间内存的差异,来实现java程序在各平台下并发一致性,再就是,java内存模型并不是真实存在的,他只是一种抽象概念,定义了线程和主内存之间的抽象关系,也就是线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存存储了该线程共享变量的副本。

2、java内存模型的三大特性:java内存模型有三大特性,原子性、可见性、有序性。
原子性:要么执行,要么不执行,主要使用互斥锁Synchronize或者lock来保证操作的原子性;
可见性:在变量修改后将新值同步回主内存,主要有两种实现方式,一是volatile,被volatile修饰的变量发生修改后会立即刷新到主内存;二是使用Synchronize或者lock,当一个变量unlock之前会将变量的修改刷新到主内存中;
有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序不会影响单线程的执行结果,却会影响多线程并发执行的正确性。主要有两种方式确保有序性:volatile 和 Synchronize 关键字,volatile是通过添加内存屏障的方式来禁止指令重排序,也就是重排序是不能把后面的指令放到内存屏障之前执行;Synchronize是保证同一时刻有且只有一个线程执行同步代码,类似于串联顺序执行代码。

延伸二:你了解先行发生原则(happens-before)吗?

为什么会出现先行发生原则:从上边我们也能看到,如果java内存模型中所有的有序性都要靠volatile和Synchronize来实现的话,那么是非常繁琐的,所以j就出现这么一个《先行发生原则》,用来判断数据是否存在竞争、线程是否安全的重要依据。
先行发生原则是java内存模型用来定义两个操作之间的偏序关系。比如说A操作先发生于B操作,那么在B操作发生之前,A操作修改了内存中的共享变量,那么就会被B操作察觉到。

延伸三:volatile的作用,volatile跟Synchronize的区别

volatile的作用:volatile关键字主要作用是确保可见性跟有序性,当一个共享变量被volatile修饰,如果一个线程修改了这个共享变量,那么其他线程就会立马可知,强制刷新到主内存。

volatile跟Synchronize的区别:
volatile只能作用局部变量,Synchronize可作用局部变量、方法、类、同步代码块等;
volatile只能保证可见性和有序性,不能保证原子性,Synchronize三者都可以保证。
volatile不会造成线程阻塞,Synchronize可能会造成线程阻塞。
在性能方面synchronized关键字是防止多个线程同时执行一段代码,会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。

延伸四:你能说说你刚刚提到的重排序吗?

重排序是编译器和处理器为了优化程序性能而对指令进行重新排序的一种手段。重排序可以保证最终执行的结果是与程序顺序执行的结果一致,并且只会对不存在数据依赖性的指令进行重排序,重排序在单线程下对最终执行结果是没有影响的,但是在多线程下就会存在问题。
举个例子:https://www.cnblogs.com/niceyoo/p/12549327.html

你能说一下Synchronize底层原理吗?

synchronized的底层原理是跟monitor有关,也就是视图器锁,每个对象都有一个关联的monitor,当Synchronize获得monitor对象的所有权后会进行两个指令:加锁指令monitorenter跟减锁指令monitorexit。
monitor里面有个计数器,初始值是从0开始的。如果一个线程想要获取monitor的所有权,就看看它的计数器是不是0,如果是0的话,那么就说明没人获取锁,那么它就可以获取锁了,然后将计数器+1,也就是执行monitorenter加锁指令;monitorexit减锁指令是跟在程序执行结束和异常里的,如果不是0的话,就会陷入一个堵塞等待的过程,直到为0等待结束。

Synchronize在JDK1.6之后做了什么样的优化?

自旋锁 锁消除 锁粗化 轻量级锁 偏向锁
参考资料:https://www.cnblogs.com/jiangds/p/6476293.html

.Synchronized 加在普通方法上锁的对象是什么,加在静态方法上锁住的对象是什么?

Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称“对象锁”。
Synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。

Synchronized(this) 和 Synchronized (User.class) 的区别 :
1.对于静态方法的,由于jvm加载的时候还没有对象产生,所以只能使用类锁,就是类名.class ,只要使用类锁就会拦截所有线程,只让一个线程可以访问
2.对于普通方法,默认的使用对象锁,可以直接使用this来表示。对于同一个对象的话,是按照顺序进行访问,对于不同对象可以同时访问。
参考:https://blog.csdn.net/qq_21479345/article/details/100574968

Synchronized和Lock的区别

从存在层次 锁的获取 锁的类型 锁的状态 锁的释放 性能方面来说
他们都是可重入锁
存在层次:Synchronizeds是java的关键字,lock是一个接口
获取锁:Synchronizeds获取锁的话是假设A获取了锁,那么B会等待,假如A阻塞了,那么B会一直等待;Lock获取锁的方法比较多,常见的是lock()方法和trylock()方法
锁的状态:Synchronizeds无法判断锁的状态,Lock可以判断锁的状态
锁的类型:Synchronizeds可重入,不可中断,非公平; Lock的话可重入可判断可公平
锁的释放:Synchronizeds可以自动释放锁;Lock必须在finally中释放锁,不然容易造成死锁
适用性:Synchronizeds适合于少量同步代码的,Lock适合大量同步代码的
图片不清晰:https://blog.csdn.net/u012403290/article/details/64910926

何谓悲观锁与乐观锁?乐观锁和悲观锁的使用场景

悲观锁:就是把事情往最坏的方向去考虑,每次去拿数据的时候都认为会对数据进行更改,所以只要有人拿数据就会上锁,其他人只能等待直到他拿完了才能去拿。我们比较常见的悲观锁有Synchronized,ReentrantLock
乐观锁:把事情都往好的方向去考虑,每次去拿数据的时候都会认为不会对数据进行更改,但是他会在更新的时候去判断一下数据有没有被更改。乐观锁适用于读数据比较多的情况,可以提高吞吐量。悲观锁的话更适合于写比较多的情况。
参考资料:https://blog.csdn.net/qq_34337272/article/details/81072874

四、线程工具类

ThreadLocal的作用和原理?

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说则无法取到数据。
ThreadLocal的实现原理:
ThreadLocal的实现原理主要是依据它的get和set方法,从set()方法的源码中我们可以了解到他是先调用getmap()方法获取当前线程的ThreadLocalMap对象,类似于我们的hashmap 是以键值对的方式来保存数据的,key是TheadLocal,当前的线程,value是保存的值;从get()方法的源码我们同样也可以发现,他是先调用getmap()方法获取当前线程的ThreadLocalMap对象,然后根据key的值来获取对应的value,由于所有操作都是基于同一个ThreadLocalMap对象,所以在多线程中可以互不干扰的存储和修改数据。

http三次握手,四次挥手(待解决,说的有点复杂)

你可能感兴趣的:(Java基础部分高频面试题总结)