==和equals的区别是什么?
==对于基本类型来说是值比较,对于引用类型来说比较的是引用;而equals默认情况下就是==,只是很多类重写了equals方法,比如string、integer等把它变成了值比较。&和&&区别是什么?
相同点:&和&&都可以用作逻辑与的运算符,表示逻辑与(and)。
不同点:&&具有短路的功能,而&不具备短路功能。当&运算符两边表达式结果都为false时,整个运算符结果为false,而&&运算符第一个为false时,则结果为false,不再计算第二个表达式。
string、stringbuffer、stringbuilder三者之间的区别是什么
string是不可变字符串,stringbuffer和stringbuilder是可变字符串。string每次对字符串进行操作都会新开辟一块内存,因此对字符串进行拼接操作时,采用stringbuffer或者stringbuilder会节省很多内存。
stringbuffer是线程安全的,stringbuilder是线程不安全的,因此stringbuilder的性能会比stringbuffer好一些。final、finally、finalize的区别与用法?
final:java中的关键字,修饰符,可以修饰类、方法、变量。- 如果一个类被声明为final,就意味着它不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract抽象类和final类
- 被声明为final的方法只能被使用,不能被重写。
- 被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
finally:java的一种异常处理机制。finally结构使代码总会执行,而不管有无异常发生。使用finally可以维护对象内部状态,并可以清理内存资源。特别是在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放在finally中,就会大大降低程序出差的几率
finalize:java中的一个方法名。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
java的基础数据类型有哪些?
byte、boolean、char、short、int、float、long、doublejava中的容器有哪些
1
2
3
4
5
6
7
8
9
10
11
12
13
14collection
list
arraylist
linkedlist
vector
stack
queue
set
hashset
linkedhashset
treeset
map
hashmap
treemapcollection和collections有什么区别?
collection是集合类的一个顶级接口。它提供了对集合对象进行基本操作的通用接口方法。collection接口在java类库中有很多具体的实现。collection接口的意义是为了各种具体的集合提供了最大化的统一操作方式,其直接继承接口有list与set等。
collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合元素进行排序、搜索以及线程安全等各种操作。arraylist和linkedlist的区别是什么?
arraylist底层的数据结构是数组,支持随机访问,查询快
linkedlist底层是双向循环链表,不支持随机访问,增删快如何实现数组和list之间的切换
list转换成为数组:调用arraylist的toarray方法
数组转换成为list:调用arrays的aslist方法说一下hashmap的实现原理
hashmap底层数据结构采用的是数据加链表的形式。当我们往hashmap中put元素时,首先根据key的hashcode重新计算hash值,根据hash值得到这个元素在数组中的位置,如果该数组在该位置上以存放其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾,如果数组中该位置没有元素,就直接将该元素放在数组的该位置上。
在jdk1.8中对hashmap的实现做了优化,当链表中节点数据超过八个之后,该链表会转化为红黑树来提高查询效率。重载和重写的区别是什么?
重写:子类继承父类时,重写父类方法。
方法名词、参数列表、返回值类型要与父类方法相同。
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方式被声明public,那么在子类中重写该方法就不能声明为protected
声明为private和final的方法以及构造方法不能被重写
重载:发生在同一类中,方法名相同,但是参数的类型或个数不能相同。访问修饰符和返回值类型可以相同也可以不同。hashcode()和equals()的联系?为什么重写equals()方法也要重写hashcode()方法?
联系:
两个对象equals为true时,hashcode也相同
两个对象hashcode不同时,equals为false
两个对象hashcode相同时,equals不一定为true
在使用hashmap或者其他hash结构的容器进行数据添加等需要对比数据是否相同的操作时,会先使用hashcode()方法进行比较,因为hashcode()比equals()要快,这样使用效率会高很多。
当重写equals()时也需要重写hashcode()方法,不重写可能就会导致hashcode()相同,但是equals()不同的情况,产生数据错误。接口和抽象类的相同点和不同点?
相同点:- 接口和抽象类都不能被实例化
- 子类都需要实现接口或抽象类的方法声明
- 都不能被final修饰
- 都可以被继承。接口被接口继承,抽象类被子类继承
不同点: - 接口需要被子类实现,抽象类需要被子类继承
- 接口只能做方法声明(jdk8中可以定义default方法体),抽象类既可以做声明,也可以做方法实现。
- 接口里只能定义pubilc static final修饰的常量,抽象类中没有限制。
hashmap和hashtable有什么区别
hashmap是线程不安全的,hashtable是线程安全的
hashmap可以允许键和值为null,hashtable不允许键和值为null如何判断hashmap是否存在某个键
一般我们在日常工作中喜欢用hashmap的get()方法判断某个键是否为null来确定是否存在某个键。但是由于hashmap是允许值为null的,所以可能有的键存在,但是值为null。这个时候可以用hashmap的containskey()方法来判断是否存在某个键。hashtable和concurrenthashmap的区别?
hashtable和concurrenthashmap都是线程安全的。不过,在进行数据操作时,hashtable会锁住所有数据,而concurrenthashmap只会锁住map的一部分数据,因此在多线程的情况下,oncurrenthashmap的性能更好。并发和并行有什么区别?
并发:同一时间按段内多个线程同时执行,但同一时刻只有一个流程执行。理论上还是单线程,相当于多个线程轮流执行
并行:同一时刻多个线程同时执行,真正的多线程请简述一下线程的生命周期
线程的声明周期有5个状态,新建状态、就绪状态、运行状态、阻塞状态、消亡状态。
新建状态:线程创建之后,此时线程和其他java对象一样,仅仅分配了内存,还不能运行。
就绪状态:当线程对象调用了start()方法后,该线程进入就绪状态,此时线程进入可运行池中,等待cpu的资源。
运行状态:处于就绪状态的线程获得cpu使用权后,执行run()方法中的方法体,此时处于运行状态。
阻塞状态:处于运行状态中的线程因为某些特殊情况,会放弃cpu的使用权,当特殊情况解除后,线程进入就绪状态,重新等待cpu的使用权。
消亡状态:当run()方法执行完毕或者线程抛出一个未捕获的异常时,线程结束,消亡。
常见的创建线程方式有哪些
继承thread类,并重写run方法,缺点是不能继承其他类
实现runnable接口,实现run方法,可同时继承其他类
实现callable接口,实现call方法
利用线程池创建线程谈谈对sleep()、wait()、yield()、jion()、notify()、notifyall()的理解
sleep():sleep方法是属于Thread类中的,sleep过程中线程不会释放锁,只会阻塞线程,让出cpu给其他线程,当指定的时间到了又会自动恢复运行状态。
wait():wait方法是属于Object类中的,wait过程中线程会释放对象锁,只有当其他线程调用notify()或notifyAll()才能唤醒此线程。wait使用时必须先获取对象锁,即必须在synchronized修饰的代码块中使用,相应的notify方法同样必须在synchronized修饰的代码块中使用,如果没有在synchronized修饰的代码块中使用时运行时会抛出异常。
yield():yield也是Thread类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁,和sleep不同的是yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。还有一点和sleep不同的是yield方法只能使同优先级或更高优先级的线程有执行的机会。
join():在当前线程中,只有等待所有调用join()方法的线程都执行完毕后,当前线程才可以继续执行。一般用于等待异步线程执行完结果之后才能继续运行的场景。
notify():唤醒(随机)一个当前对象锁上等待的线程。
notifyAll():唤醒所有当前对象锁上等待的线程。
什么是自动装箱和拆箱
装箱:就是自动将基本数据类型转换为包装器类型
拆箱:就是自动将包装器类型转换为基本数据类型
例:
//自动装箱
Integer a = 10;
//自动拆箱
int b = a;java的三大特性是什么
java的三大特性是:封装、继承、多态。- 封装:将属性和方法归到一个类中。方法公开,属性私有,让用户只能通过方法来访问属性,提高数据安全性且便于代码维护。
- 继承:子类继承父类非私有的属性和方法,提高代码的复用性。java只支持单继承,不支持多继承。
- 多态:多态可以理解为同一个行为发生在不同的对象上会产生不同的结果。比如:猫和狗都是动物,但是叫声是不一样的。
多态三要素继承、方法重写、父类引用指向子类对象。
- 什么是序列化和反序列化
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
对象的序列化主要用途有两个:- 把对象的字节序列写入文件保存在硬盘中,实现持久化。
- 在网络上传送对象的字节序列,实现网络传输对象。在java中通过对象流(ObjectInputStream,ObjectOutputStream)来实现对象的序列化和反序列化,并且被序列化和反序列化的对象需要实现Serializable接口。
- java四种修饰符及其访问权限是什么
private修饰的变量只能在该变量的所在类内部使用。
defalut缺省修饰符,修饰的变量可以在所在类内部使用、所在包内部使用。
protected修饰的变量可以在所在类内部、所在包内部使用、所在类其他包的子类使用。
public修饰的变量可以在所在类内部、所在包内部使用、所在类其他包的子类、其他包所有类都可以使用。
throw和throws的区别是什么
thirows:在方法声明后面,表示上报异常信息给调用者,由调用者处理异常信息
throw:在方法体内部,由方法内部的语句处理,手动抛出异常什么是错误和异常
错误:
一般指程序运行时遇到的硬件或操作系统的错误,如内存溢出、不能读取硬盘分区、硬件驱动错误等。 这是致命的,将导致程序无法运行,同时也是程序本身不能处理的。
异常:
指在运行环境正常的情况下遇到的运行时错误。异常是非致命的,但也会导致程序的非正常终止。Java可以捕获和处理异常。
在java中,Throwable类是所有异常和错误的父类,Error是所有错误的父类,继承于Throwable。Exception是所有异常的父类,继承于Throwable。Exception又分为运行时异常(RuntimeException)和编译异常。
- 常见的异常有哪些
运行时异常:
RuntimeException运行时异常
NullPointerException空指针异常
ClassCastException类对象转换异常
IndexOutOfBoundsException数组越界
IlegalArgumentException非法参数异常
编译时异常:
NoSuchMethodException方法找不到异常
ClassNotFoundException类找不到异常
SQLException数据库异常
FileNotFoundException文件路径异常
IOException io异常.
如果catch中有return finally还会执行吗?finally什么情况下不会执行
如果catch中有return,finally会执行,除非在finally执行前exit方法推出了虚拟机,否则finally都会在return前执行多线程一定比单线程快吗
不一定。
在回答这个问题之前需要了解一个前提, 在单核cpu的情况下,多个线程之间是并发进行,通过抢占cpu时间片的方式轮流运行,并且线程之间的切换是需要耗费资源的。- 当线程执行的任务需要和io设备进行交互时,由于cpu运算速度要比io设备(磁盘等)要快的多,大多数时间里cpu都是在等着io设备返回处理结果,这个时候cpu可以利用这个等待的时间去执行其他的线程,在这种情况下多线程是比单线程快的。
- 当线程执行的任务是cpu密集型时(没有和io设备交互),多线程不一定比单线程快,多线程要比单线程多了线程间切换的消耗。
- 什么是反射
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。
缺点:使用反射性能较低,需要解析字节码,将内存中的对象进行解析,慢于直接直接执行java代码。
获取一个类对象有哪几种方式
Class.forName(“类的路径”)
类名.class
对象名.getClass()反射api有哪些
反射api用来生成jvm中的类、接口或者对象的信息
1.class类:反射的核心类,可以获取类的属性,方法等信息
2.field类:java.lang.reflec包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值
3.method类:java.lang.reflec包中的类,表示类的方法,可以用来获取类中的方法信息或者执行方法
4.constructor类:java.lang.reflec包中的类,表示类的构造方法
- 哪里用到反射机制
1.JDBC中,利用反射动态加载了数据库驱动程序。
2.Web服务器中利用反射调用了Sevlet的服务方法。
3.Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
4.很多框架都用到反射机制,生成实体,注入属性,调用方法,如Spring。
- 内存泄漏和内存溢出的区别是什么
内存溢出:是程序在申请内存时,没有足够的内存空间供其使用。比如:你需要10M的空间,内存空间只剩8M,这就会出现内存溢出。
内存泄漏:是程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重。最终导致内存溢出。
- 为什么要使用线程池
一个线程的生命周期可以简化为:T1创建线程时间,T2在线程中执行任务的时间,T3销毁线程时间。其中T1和T3是非常消耗系统资源的,并且由于T1的存在每次的响应速度也有所影响。因此线程池应运而生。
线程池在初始化时会自动创建一定数量的线程,需要用到时从线程池中取线程,用完后不销毁线程而是返还给线程池留着下次使用。
优点:
1.减少了线程创建和线程销毁所消耗的资源。
2.提高了响应速度。
3.减少了创建线程的数量。
- 常见的线程池有哪些
1.newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务。
2.newFixedThreadExecutor(n)
固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。
3.newCache ThreadExecutor
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
4.newSchedule ThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程。
- 为什么不建议使用executors静态工厂构建线程池
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。
private static ExecutorService executor = new ThreadPoolExecutor(10, 10,60L, TimeUnit.SECONDS,new ArrayBlockingQueue( 10));
常用参数:
corePoolSize:核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true。
maximumPoolSize:线程池允许的最大线程池数量。
keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间。
unit:超时时间的单位。
workQueue:工作队列,保存未执行的Runnable任务。
threadFactory:创建线程的工厂类。
handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。
- java锁有哪些种类
1.根据是否需要锁住目标资源可以分为悲观锁和乐观锁。
悲观锁:对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和lock的实现类都是悲观锁。
乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。JAVA中的Automic相关实现类就是乐观锁。
2.根据没获取锁时线程是否阻塞可以分为自旋锁和非自旋锁。
自旋锁:没有获得锁后,会不断的自旋(循环)去尝试获取锁,直到获得锁或者超过自旋次数后停止。适合一些任务执行时间比较短的任务。优点是避免了线程间切换的开销,缺点是自旋占用了较多cpu资源。
非自旋锁:没有获得锁后,会阻塞等待唤醒。
3.根据多个线程间竞争获取锁时是否需要排队分为公平锁和非公平锁。
4.根据一个线程获取到一把锁后,是否可以连续执行多个需要该锁的方法,可以分为可重入锁和不可重入锁。
5.根据是否可以共享锁,分为共享锁和排他锁。
- synchronized锁和lock锁的区别是什么
synchronized:是一个关键字,发生异常会自动释放锁,属于可重入锁,未获取锁时不可中断,在jdk1.5之前是重量级锁,在jdk1.6之后对其进行了锁升级等优化,性能不比Lock差。
Lock:是一个接口,一般使用其实现类ReentrantLock,需要手动释放锁,属于可重入锁,未获取锁时可以中断。
- 什么是cas算法
CAS全称compare and swap,即比较与交换。该算法是一种无锁算法,可以在不加锁的前提下保证线程安全,即在没有线程被阻塞的情况下实现变量同步,因此属于非阻塞同步的范畴。乐观锁就是采用CAS算法。
CAS算法涉及到三个操作数:要修改的内存值V、旧的内存值A、新的交换值B。其具体的操作步骤是,我们要修改内存值V,修改前先把当前内存值V记录在A中,然后进行数据处理,将需要修改的值记录在B中。当且仅当v等于A时,CAS算法通过原子方式用B将V替换,否则不执行任何操作。一般情况下,CAS算法是一个自旋操作,即不断的重试直到成功为止。适合读多写少的场景。
jdk5增加了并发包java.util.concurrent,其下面的Automic相关实现类使用CAS算法实现乐观锁。
存在问题:
1.ABA问题。若变量V初次读取时为A,在准备赋值时检查其仍为A,但这不能说明它没有被其他线程修改过,因为在这段时间它可能被改为其他值,然后又改为A,此时CAS算法就会误认为它从未修改过。该问题被称为CAS算法的ABA问题。在实际使用场景中需判断,若ABA问题对该使用场景的影响不大,可以不作处理。否则,可以采取对每次修改添加版本号标识的方式解决ABA问题,或直接采用悲观锁。
2.存在自旋开销。CAS算法是:一个自旋操作,在失败时会不断重试直到成功为止。但这种自旋如果长时间存在,将会对CPU造成很大的负担。一般会设置自旋最大次数,超过这个次数就会停止自旋退出竞争。
- synchronized锁升级?
jdk1.5之前是synchronized锁是重量级锁,性能很差。经过研究发现,被锁住的资源在大多数形况下是不存在线;程竞争的,因此jdk1.6之后对synchronized锁进行了优化,引入了偏向锁和轻量级锁。目前锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。
在讲锁升级之前需要了解一些概念,Java对象头和Monitor。
1.Java对象头:synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的。对象头主要包括两部分数据: Mark Word (标记字段)、Klass Pointer (类型指针)。
(1)Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。
(2)Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
2.Monitor: Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的MutexLock(互斥锁)来实现的线程同步。
锁的四种状态:
1.无锁: Mark Word存储01,当一个对象被创建之后,还没有线程进入,这个时候对象处于无锁状态,其MarkWord中的信息如.上表所示。
2.偏向锁: Mark Word存储01,当锁处于无锁状态时,有一个线程A访问同步块并获取锁时,会在对象头和栈帧中的锁记录记录线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来进行加锁和解锁,只需要简单的测试一下啊对象头中的线程ID和当前线程是否一致。
3.轻量级锁: Mark Word存储00,在偏向锁的基础上,又有另外一个线程B进来,这时判断对象头中存储的线程A的ID和线程B不一致,就会使用CAS竞争锁,并且升级为轻量级锁,会在线程栈中创建一个锁记录,将MarkWord复制到锁记录中,然后线程尝试使用CAS将对象头的Mark Word替换成指向锁记录的指针,如果成功,则当前线程获得锁;失败,表示其他线程竞争锁,当前线程便尝试CAS来获取锁。
4.重量级锁: Mark Word存储10,当线程没有获得轻量级锁时,线程会CAS自旋来获取锁,当一个线程自旋10次之后,仍然未获得锁,那么就会升级成为重量级锁。成为重量级锁之后,线程会进入阻塞队列,线程不再自旋获取锁,而是由CPU进行调度,线程串行执行。
- 什么是voliate关键字
在学习voliate关键字之前,先了解一下java的内存模型。要知道所有线程的共享变量都存储在主内存中,每一个线程都有一个独有的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自己的工作内存中,只操作工作内存中的数据。当修改完毕后,再把修改后的结果从工作内存中写回主内存中。数据刷新也是一样。从工作内存写回主内存以及将主存的数据刷新回工作内存的这个时间不确定,因此存在一个缓存与主存数据不一致的问题。
voliate修饰变量的作用:
1.内存可见性:基于缓存一致性协议,当用voliate关键字修饰的变量改动时,cpu会通知其他线程,缓存已被修改,需要更新缓存。这样每个线程都能获取到最新的变量值。一般多线程中设置一个循环退出标志时需要加上voliate修饰,否则标志修改后,正在循环中的线程还是用的旧值,无法跳出循环。
2.基于内存屏障的防止指令重排:机算机在执行程序时,为了提高性能,编译器和处理器常常会在不影响语义的情况下对指令的顺序进行调整。用voliate修饰的变量,可以防止cpu指令重排序。
在双重检查单例模式下,需要用volatile修饰单例变量。因为instance = new Singleton();初始化对象的过程其实并不是一个原子的操作,它会分为三部分执行:
(1)给instance分配内存
(2)调用instance的构造函数来初始化对象
(3)将instance对象指向分配的内存空间(执行完这步instance就为非null了)
步骤2和3不存在数据依赖关系,如果虚拟机存在指令重排序优化,则步骤2和3的顺序是无法确定的。如果A线程率先进入同步代码块并先执行了3而没有执行2,此时因为instance已经非nlull。这时候线程B在第一次检查的时候,会发现instance已经是非null了,就将其返回使用,但是此时instance实际上还未初始化,自然就会出错。所以我们要限制实例对象的指令重排,用volatile修饰(JDK5之前使用了volatile的双检锁是有问题的)。
- threadlocal会产生内存泄漏吗
会。threadlocal使用完如果没有及时清理,可能会产生内存泄漏。因此,每次使用完threadlocal后,需要及时remove()