java面试知识点(三)

实现多线程的两种方法:Thread与Runable:

Thread类实现了Runnable。

用户线程和守护线程:

这儿加一个知识点:User Thread(用户线程)、Daemon Thread(守护线程),用一个通俗的说法,任何一个守护线程都是JVM中所有用户线程的保姆。只要JVM中有一个用户线程没有结束,所有的守护线程都得工作。当所有的用户都退出时,守护线程随着JVM一同退出。Daemon就是为User提供便捷的服务,例如GC。

他俩几乎没有区别,唯一不同就是JVM的离开。

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

(2) 在Daemon线程中产生的新线程也是Daemon的。

(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。

线程同步方法:synchronized、lock、reentrantLock等:

synchronized:java语言的关键字,他是在软件层面依赖JVM实现同步。synchronized方法控制对类成员变量的访问:每个类实例都对应一把锁,每个synchronized方法都必须获得调用该方法的类实例的所才能执行,这种机制确保了同一时刻对于每一个类实例,其所有声明为synchronized的成员函数中至多只有一个处于可执行状态,从而避免了类成员变量的访问冲突。

synchronized的方法的缺陷:若将一个大的方法声明为synchronized将会大大影响效率。解决synchronized方法的缺陷:通过synchronized关键字来生命synchronized块。在修饰代码块的时候需要一个reference对象作为锁的对象.在修饰方法的时候默认是当前对象作为锁的对象.在修饰类时候默认是当前类的Class对象作为锁的对象.

Lock:lock接口提供了比使用synchronized方法可语句更广泛的锁定操作,它允许更灵活的结构,具有差别很大的属性,支持多个相关的Condition对象,在硬件层面支持特殊的cpu指令实现同步更加灵活。(Condition 接口将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。)

ReentrantLock:一个可重入的互斥锁,他具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。重入性:值得是同一个线程多次试图获取他所占有的锁,请求会成功。当释放锁的时候,直到重入次数为零,锁才释放完毕。ReentrantLock 的lock机制有2种,忽略中断锁响应中断锁。假如A、B竞争一个锁,A获得了锁,但是A迟迟不释放锁,这时候ReentrantLock提供了两种机制:1B释放自己,但是ReentrantLock不去响应 2B线程中断自己(或者别的线程中断它),ReentrantLock 处理了这个中断,并且不再等待这个锁的到来,完全放弃.

synchronized和lock的用法与区别:synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。Lock用的是乐观锁方式。每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试(轮询操作),直到成功为止。

一般情况下都是用synchronized原语实现同步,除非下列情况使用ReentrantLock①某个线程在等待一个锁的控制权的这段时间需要中断②需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程③具有公平锁功能,每个到来的线程都将排队等候

锁的等级:方法锁、对象锁、类锁:方法锁就是对象锁,synchronized修饰的方法或者代码块必须得获得类的实例,这样能控制实例中的变量在某个时间段内只能被一个线程访问。因为synchronized的成员函数之多只能有一个可处于执行状态。而类锁是在类中某些静态变量或者静态函数需要加锁,所以利用Class来作为锁。类锁仅仅是一个名字,这样能控制不同实例之间共享该类的Class对象。

生产者消费者模式实现:是并发、多线程编程中经典的设计模式,通过分离的执行工作解耦,简化了开发模式。如何使用阻塞队列解决生产者消费者模式,以及使用生产者消费者模式的好处。

实现生产者消费者模式可以用不同的方式来实现,经典的方法是使用wait/notify方法在生产者和消费者线程中合作,在队列满了或者队列满了或者队列是空的条件阻塞。java5中阻塞队列(BlockingQueue)数据结构更简单,因为它隐含的提供了这些控制。

ThreadLocal的设计理念与作用:作用是提供县城内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立,也就是说,每个线程的ThreadLocal变量是自己的,其他线程是访问不到的。ThreadLocal最常用以下这个场景:多线程环境下存在对非线程安全u对象的并发访问,而且该对象不需要在线程间共享,但是我们不想加锁,这个时候就可以使用ThreadLocal来使得每个线程都持有一个该对象的副本。

ThreadPool用法与优势:1 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 2 提高响应速度,当任务到达时,任务不需要等待线程创建就可以建立即执行 3 提高线程的可管理性。 线程是稀缺资源如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控,但是要做到合理的利用线程池,必须对其原理了如指掌。

线程池用法:Executors中有很多静态方法可以用来创建线程池;Executor是一个工厂方法类;里面可以创建很多线程;所有的ThreadPool都实现了Executor这个接口;因此只要返回Executor(该接口只有一个方法,那就是executor(Runnable thread))就可以了。ExecutorService cachedThreadPool = Executors.newCachedThreadPool;cachedThreadPool.execute(new Runnable public void run { System.out.println(index); } 也执行一次execute方法,就产生一个新的线程;

new  ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);

wait和sleep的区别:

1 wait是Object类中,与notify和notifyAll搭配使用,sleep属于Thread类的;

2 wait()睡眠时,释放对象锁。而sleep睡眠时,保持对象锁,仍然占有该锁。(注意:wait()使用notify或者是notifyAll或者指定睡眠的时间来唤醒当前等待池中的线程,并且必须放在synchronized block中,否则会报异常)

foreach与正常for循环效率对比:

foreach:编译成字节码后,使用的是迭代器实现的。特点:1 无需获取容器的大小,2 需要创建额外的迭代器变量, 3 遍历期间得到的是对象,没有索引位置,因此不能赋值操作。

for:for需要获取容器的大小,如果计算大小比较耗时的话,那么for循环效率会很低。for循环是根据容器大小来防止越界的,因此每次循环都需要进行一次比较。


java面试知识点(三)_第1张图片
时间效率相差不是很大

Java IO与NIO:

NIO:non-blocking IO,它是一种非阻塞式io方式,

第一个重大差异是IO是面向流的,而NIO是面向缓存区的。这句话是什么意思呢?

Java IO面向流意思是我们每次从流当中读取一个或多个字节。怎么处理读取到的字节是我们自己的事情。他们不会再任何地方缓存。再有就是我们不能在流数据中向前后移动。如果需要向前后移动读取位置,那么我们需要首先为它创建一个缓存区。

Java NIO是面向缓冲区的,这有些细微差异。数据是被读取到缓存当中以便后续加工。我们可以在缓存中向向后移动。这个特性给我们处理数据提供了更大的弹性空间。当然我们任然需要在使用数据前检查缓存中是否包含我们需要的所有数据。另外需要确保在往缓存中写入数据时避免覆盖了已经写入但是还未被处理的数据

Java IO的各种流都是阻塞的。这意味着一个线程一旦调用了read(),write()方法,那么该线程就被阻塞住了,知道读取到数据或者数据完整写入了。在此期间线程不能做其他任何事情。

Java NIO的非阻塞模式使得线程可以通过channel来读数据,并且是返回当前已有的数据,或者什么都不返回如果但钱没有数据可读的话。这样一来线程不会被阻塞住,它可以继续向下执行。

NIO允许我们只用一条线程来管理多个通道(网络连接或文件),随之而来的代价是解析数据相对于阻塞流来说可能会变得更加的复杂

java反射和原理:

首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化根据你写的代码。

反射的常用类和函数:Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;其中class代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class类,它是实现反射的基础

但是反射也有缺点,比如性能比较低、安全性比较复杂等

泛型常用特点,List能否转为List:

泛型是java SE5引入的新特性,泛型实现了参数化类型的概念。在以往的时候,通常都是通过Object来进行操作,这个时候就是操作最多的就是对该object进行数据的强制转换,而这种转换是基于开发者对数据类型明确的情况下。如果不一致,编译时期是不会报错的,但运行时期就会报错。相比之下,使用泛型的好处,就可以在编译时期进行安全检查,并且在运行时所有的转换都是强制的,隐式的,大大提高了代码的重用率。

List是不能转为List,但是可以转为List。因为泛型并不具有继承性。只是一个限定的作用。(经过实验确实如此,大家可以自己去试试,我刚开始也是不信)至于泛型,大家可以去看这篇文章

解析xml的几种方式的原理和特点:DOM SAX PULL

DOM:(XML Document Object model),XML文件对象模型,定义了访问和操作xml文档元素的方法和接口。原理:DOM是基于树形结构的节点的文档驱动方式,使用DOM对xml文件按进行操作时,首先解析器读入整个xml文档到内存中,然后解析全部文件,并将文件分为独立的元素、属性等。以树结构的形式在内存中对xml文件进行表示,开发人员通过使用DOM api遍历xml树,根据需要修改文档或者检索所需的数据。

Dom解析的步骤:1、调用 DocumentBuilderFactory.newInstance() 方法得到 DOM 解析器工厂类实例。2、调用解析器工厂实例类的 newDocumentBuilder() 方法得到 DOM 解析器对象   3、调用 DOM 解析器对象的 parse() 方法解析 XML 文档得到代表整个文档的 Document 对象。

优点:整个文档树存在内存中,可对XML文档进行操作:删除、修改等等;可多次访问已解析的文档;由于在内存中以树形结构存放,因此检索和更新效率会更高。;

缺点:解析 XML 文件时会将整个 XML 文件的内容解析成树型结构存放在内存中并创建新对象,比较消耗时间和内存;

使用情境:对于像手机这样的移动设备来讲,内存是非常有限的,在XML文档比较小、需要对解析文档进行一定的操作且一旦解析了文档需要多次访问这些数据的情况下可以考虑使用DOM方式,因为其检索和解析效率较高

PULL:和SAX方式一样,都是基于事件驱动(和观察者模式有点像),可直接根据需要读取所需的xml数据。PULL提供了开始元素和结束元素,当某个元素开始时,我们可以调用parser,nextText从xml文档中提供所有字符数据。与SAX不同的是,在PULL解析过程中触发相应的事件调用方法返回的是数字,且我们需要自己获取产生的事件然后做出相应的操作,而不像SAX那样有处理器触发一种事件的方法从而执行代码。当执行到一个文档结束时,自动生成EndDocument事件。

优点:SAX的优点PULL都有,而且解析方法比SAX更加简单

缺点:可拓展性差:无法对 XML 树内容结构进行任何修改

使用情境:适用于需要处理大型 XML 文档、性能要求较高、不需要对解析文档进行修改且不需要对解析文档多次访问的场合

SAX:基于事件驱动,在读取xml文档内容时,事件源顺序的对文档进行扫描,当扫描到文档的开始和结束标签、节点元素的开始和结束标签时,直接调用对应的方法,并将状态信息以参数的形式传递到方法中,然后我们可以依据状态信息来执行相关的自定义操作。解析可能比PULL复杂。

优点:解析效率高、占存少、灵活性高

缺点:解析方法复杂(API接口复杂),代码量大;可拓展性差:无法对 XML 树内容结构进行任何修改

使用情境:适用于需要处理大型 XML 文档、性能要求较高、不需要对解析文档进行修改且不需要对解析文档多次访问的场合

Java1.5、1.7与1.8新特性:

JDK1.5:1 自动装箱与拆箱:基本类型与包装类型自动互换 2 枚举类型的引入 3 静态导入:import static,可直接使用静态变量与方法 4 可变参数类型 5 泛型 6 for-each循环 

JDK1.7:1 switch允许传入字符串 2 泛型实例化类型自动推断:List tempList = new ArrayList<>() 3 对集合的支持,创建List / Set / Map 时写法更简单了,如:List< String> list = ["item"],String item = list[0],Set< String > set = {"item"}等 4 允许在数字中使用下划线 5 二进制符号加入,可用作二进制字符前加上 0b 来创建一个二进制类型:int binary = 0b1001_1001  6 一个catch里捕捉多个异常类型,‘|’分隔

JDK1.8: 1 允许为接口添加默认方法,又称为拓展方法,使用关键字default实现 2 Lambda 表达式 3 Date API 4 多重注解 

JNI的使用:java native interface(java 本地方法调用)

是一种实现java层与Native层交互的技术,有时为了追求效率问题,或者是使用native代码编写的函数库,我们就不得不使用JNI接口。

我们在Java中使用JNI接口只需要两步:1 使用native关键字声明某方法为本地方法 2 使用System.loadLibrary加载由C/C++编写成的动态链接库

public class Main {

public static void main(String[] args) throws Exception {

// 动态库名字,windows平台自动拓展成makeStr_jni.dll

System.loadLibrary("makeStr_jni");

// 打印字符串

printString("Java World!");

}// native关键字表示为本地方法

public static native void printString(String str);

}

动态代理:

动态代理的使用流程简单说一下动态代理步骤:(可以先了解一些代理的好处,对后面的实现原理的理解有帮助)

 1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法  

2.创建被代理的类以及接口

3.通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理

4.通过代理调用方法

实在不会可以看看这个 点我

实现原理:因为JDK生成的最终真正的代理类,它继承自Proxy并实现了我们定义的Subject接口,在实现Subject接口方法的内部,通过反射调用了InvocationHandlerImpl的invoke方法。

通过分析代码可以看出Java 动态代理,具体有如下四步骤:

1 通过实现 InvocationHandler 接口创建自己的调用处理器;

2 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;

3 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

4 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

你可能感兴趣的:(java面试知识点(三))