JavaSe面试题
JavaSe面试题
请你说说多线程
1.线程是程序执行的最小单元,一个进程可以拥有多个线程 2.各个线程之间共享程序的内存空间(代码段、数据段和堆空间)和系统分配的资源(CPU,I/O,打开的文件),但是各个线程拥有自己的栈空间 3.多线程优点:减少程序响应时间;提高CPU利用率;创建和切换开销小;数据共享效率高;简化程序结构
说说线程的创建方式
创建线程有三种方式,分别是继承Thread类,实现Runnable接口,实现Callable接口,继承Thread类之后我们需要重写run()方法,方法中是我们希望这个线程执行什么操作,再创建对象的实例,通过实例对象的start()方法开启这个线程,实现runnable接口之后,我们需要实现run方法,方法中同样写我们需要执行的操作,然后将实现了接口的类作为参数创建一个Thread对象,通过这个对象的start方法开启线程,实现Callable之后,需要实现call方法,方法中写我们需要的操作,然后创建实现接口类的对象,将对象作为参数创建FurtureTask对象,再将task对象作为参数创建thread对象,调用start方法开启线程,还可以使用task对象的get方法获取返回值。他们的区别是前二者不能获取返回值,callable接口可以获得返回值,一般在实际使用中,更多使用实现接口的方式开启线程,因为接口不会占用类的继承位置。
说说怎么保证线程安全
多个线程同时操作共享资源时,就会出现线程安全问题。这时就需要我们使用同步方案保证线程安全,常见的保证线程安全的方式有:Atomic原子类(底层使用CAS算法实现)、volatile关键字(1.可见性、2.防止指令重排、3.不保证原子性)、Lock类、锁(JUC包下的lock锁)、synchronized关键字等保证线程安全,使用ThreadLocal可以为每一个线程单独保存一份数据,避免了多线程访问共享变量。
说说乐观锁和悲观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。在Java中,synchronized从偏向锁、轻量级锁到重量级锁,全是悲观锁。JDK提供的Lock实现类全是悲观锁。
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下,在此期间有没有别人去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS。
说说synchronize的用法及原理
一、用法:1. 静态方法上,则锁是当前类的Class对象。 2. 作用在普通方法上,则锁是当前的实例(this)。 3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。 能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。
二、原理:底层是采用Java对象头来存储锁信息的,并且还支持锁升级。在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步,
synchronized和Lock有什么区别
synchronized是同步锁,可以修饰静态方法、普通方法和代码块。修饰静态方法时锁住的是类对象,修饰普通方法时锁住的是实例对象。当一个线程获取锁时,其他线程想要访问当前资源只能等当前线程释放锁。 synchronized是java的关键字,Lock是一个接口。 synchronized可以作用在代码块和方法上,Lock只能用在代码里。 synchronized在代码执行完或出现异常时会自动释放锁,Locl不会自动释放,需要在finally中释放。 synchronized会导致线程拿不到锁一直等待,Lock可以设置获取锁失败的超时时间。 synchronized无法获知是否获取锁成功,Lock则可以通过tryLock判断是否加锁成功。
Java哪些地方使用了CAS
1、CAS 比较并交换,比较典型的使用场景有原子类、AQS、并发容器。 2、AQS:在向同步队列的尾部追加节点时,它首先会以CAS的方式尝试一次,如果失败则进入自旋状态,并反复以CAS的方式进行尝试。 3、并发容器:以ConcurrentHashMap为例,它的内部多次使用了CAS操作。在初始化数组时,以CAS的方式修改初始化状态,避免多个线程同时进行初始化。在执行put方法初始化头节点时,它会以CAS的方式将初始化好的头节点设置到指定槽的首位,避免多个线程同时设置头节点。
说说你对ThreadLocal的理解
ThreadLocal即线程变量,它用于共享变量在多线程中的隔绝,即每个线程都有一个该变量的副本彼此互不影响也就不需要同步机制了,实现原理:每个Thread对象中都有一个ThreadLocal类的内部类ThreadLocalMap对象,他是一个键值形式的容器(键使用弱引用修饰),以ThreadLocal对象的get和set方法来存取共享变量值,原理时:以ThreadLocal对象作为key来存取共享变量值。
一个ThreadLocal用完后必须remove,否则会造成内存泄漏。由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
当ThreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值,但是通过这种方式是不可靠的。
请你说说HashMap底层原理
在1.8之前,hashmap的底层是数据+链表,在之后变成了数组+链表+红黑树,当链表节点大于8的时候,就变成了红黑树,当小于6的时候就变成链表,中间有个7作为缓冲,以免在数据不断的插入和删除的时候链表和红黑树不断变换,影响性能。 在定位元素的时候,通过hash算法来确定元素在数组中位置,如果说想要存储的位置已经有元素了,那么会发生哈希碰撞,一般情况下有开放地址法和拉链法,hashmap采用拉链法解决哈希冲突。 扩容的话,hashmap初始值为16,默认负载因子为0.75,每次扩容都以2的倍数进行扩容。
说说类加载机制
java中的类不是一开始就全部加载好的,因为这样对性能和内存的要求很高,而是使用到某一个类才加载某一个类。一个类对象的生命周期包括:加载,验证,准备,解析,初始化,使用和卸载这7个周期,而类加载这是前5个步骤。加载过程主要完成:1,将类按照其全限定名获取其二进制字节流文件。2,将获得的二进制流文件中的静态结构转换为方法区的运行时结构,3,在方法区中生成该类的类对象作为数据的入口。验证过程主要是检查该二进制字节流是否符合当前虚拟机标准避免发生安全问题。准备阶段主要是将类中定义的类变量按照固定的标准进行分配空间和第一次初始化,这里不会为实例变量分配,而final变量在编译过程中就已经被分配了。解析阶段是将常量池中的符号引用转化为直接引用的过程(例如存入某个字面量进入运行时常量池)。初始化阶段执行类的构造器的阶段,包括静态代码块的执行,jvm会使用同步机制来保证初始化阶段的类构造器指挥被执行一次从而避免类的重复加载。
请你说一下抽象类和接口的区别
- 抽象类多用于在同类事物中有无法具体描述的方法的场景,而接口多用于不同类之间,定义不同类之间的通信规则。
- 接口只有定义,而抽象类可以有定义和实现。
- 接口需要实现implement,抽象类只能被继承extends,一个类可以实现多个接口,但一个类只能继承一个抽象类。
- 抽象类倾向于充当公共类的角色,当功能需要累积时,用抽象类;接口被运用于实现比较常用的功能,功能不需要累积时,用接口。
请你说说泛型、泛型擦除
泛型:Java在jdk1.5引入了泛型,在没有泛型之前,每次从集合中读取的对象都必须进行类型转换,如果在插入对象时,类型出错,那么在运行时转换处理的阶段就出错;在提出泛型之后就可以明确的指定集合接受哪些对象类型,编译器就能知晓并且自动为插入的代码进行泛化,在编译阶段告知是否插入类型错误的对象,程序会变得更加安全清晰。
泛型擦除:Java泛型是伪泛型,因为Java代码在编译阶段,所有的泛型信息会被擦除,Java的泛型基本上都是在编辑器这个层次上实现的,在生成的字节码文件中是不包含泛型信息的,使用泛型的时候加上的类型,在编译阶段会被擦除掉,这个过程称为泛型擦除。
说说你了解的Jvm内存模型
- 类装载子系统:主要通过类的全限定名来加载类
- 字节码执行引擎:是用于执行被载入类的方法的指令。
- 运行时数据区:主要包括虚拟机栈(又叫线程栈,每有一个线程就会创建一个线程栈,主要存储用于线程操作的数据,分为局部变量表,操作数栈,动态链接和方法出口),本地方法栈(和虚拟机栈类似,它主要用于存储本地方法中的一些相关数据),程序计数器(用于记录正在执行的虚拟机字节码指令的位置或地址),堆(主要用于存储创建出来的对象,是垃圾回收的主要区域)和方法区(存储已经被加载的类信息,常量,静态变量等,也是垃圾回收的主要区域);
请你说说Java的四种引用方式
java中的四种引用方式分别是:
- 强引用,以new关键字创建的引用都是强引用,被强引用引用的对象永远都不会被回收。
- 软引用:以SoftRererenc引用对象,被弱引用引用的对象只有在内存空间不足时会被垃圾回收。
- 弱引用,以WeakReference引用对象,被弱引用引用的对象一定会被回收,它只能存活到下一次垃圾回收。
- 虚引用:以PhantomReference引用对象,一个对象被引用引用后不会有任何影响,也无法通过该引用来获取该对象,只是其再被垃圾回收时会收到一个系统通知。