1、JDK和JRE区别?
JDK是整个JAVA的核心,包括了Java运行环境JRE,一堆Java工具和Java基础的类库。
通过JDK开发人员将源码文件(java文件)编译成字节码文件(class文 件)。
JRE是Java运行环境,不含开发环境,即没有编译器和调试器。将class文件加载到内存准备运行。
2、final关键字,抽象类可以使用final修饰吗?
1.用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的 值无法被改变。对于成员变量来讲,必须在声明时或者构造方法中对它赋值;
2.修饰方法,表示该方法无法被重写;
3.修饰类,表示该类无法被继承。
注:抽象类是被用于继承的,final修饰代表不可修改、不可继承的。所以不能用final修饰抽象类。
3、JAVA容器
(1)ArrayList底层数组实现,封装了常见的增删改查操作,并且支持动态扩容。适合查找多的场合。
(2)LinkedList基于链表实现的列表。适合增删情况较多的场合。
(3)TreeSet,基于二叉排序树(红黑树)实现的。TreeSet里最典型的就是它用到了两种排序方式,即基于元素对象自身的实现的Comparable接口的自然排序,以及基于更为灵活不与单个元素绑定的Comparator接口的客户化排序。自己在构造的时候传入一个比较器即可。
(4)HashMap是用来存储键值对的映射关系,底层是用数组+链表实现的。结合put操作讲一下。
(5)HashSet其实就是基于HashMap实现的,只不过将值固定为一个固定的值。
(6)LinkedHashMap,支持按照插入顺序排序。
(7)PriorityQueue优先级队列,一个基于优先级堆的无界优先级队列
4、多线程安全在三个方面体现:
1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作;
2.可见性:一个线程对主内存的修改可以及时地被其他线程看到;
3.有序性:程序执行的顺序按照代码的先后顺序执行,由于指令重排序,结果一般杂乱无序。
5、JAVA怎么保证线程安全?
(1)保证原子性
常用的保证Java操作原子性的工具是锁和同步方法(或者同步代码块)。
使用锁,可以保证同一时间只有一个线程能拿到锁,也就保证了同一时间只有一个线程能执行申请锁和释放锁之间的代码。
与锁类似的是同步方法或者同步代码块。
使用非静态同步方法时,锁住的是当前实例;使用静态同步方法时,锁住的是该类的Class对象;使用静态代码块时,锁住的是synchronized关键字后面括号内的对象。
无论使用锁还是synchronized,本质都是一样,通过锁来实现资源的排性,从而实际目标代码段同一时间只会被一个线程执行,进而保证了目标代码段的原子性。这是一种以牺牲性能为代价的方法。
(2)保证可见性
Java提供了volatile关键字来保证可见性。
由于JMM是基于共享内存实现线程通信的,所以会存在缓存一致性的问题。
当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效。
因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。
(3)保证顺序性
编译器和处理器对指令进行重新排序时,会保证重新排序后的执行结果和代码顺序执行的结果一致,所以重新排序过程并不会影响单线程程序的执行,却可能影响多线程程序并发执行的正确性。
Java中可通过volatile在一定程序上保证顺序性,另外还可以通过synchronized和锁来保证顺序性。
synchronized和锁保证顺序性的原理和保证原子性一样,都是通过保证同一时间只会有一个线程执行目标代码段来实现的。
除了从应用层面保证目标代码段执行的顺序性外,JVM还通过被称为happens-before原则隐式地保证顺序性。
两个操作的执行顺序只要可以通过happens-before推导出来,则JVM会保证其顺序性,反之JVM对其顺序性不作任何保证,可对其进行任意必要的重新排序以获取高效率。
6、有没有其他方法保证线程安全?
有。尽可能避免引起非线程安全的条件——共享变量。
如果能从设计上避免共享变量的使用,即可避免非线程安全的发生,也就无须通过锁或者synchronized以及volatile解决原子性、可见性和顺序性的问题。
还有不可变对象可以使用final修饰的对象保证线程安全,由于final修饰的引用型变量(除String外)不可变是指引用不可变,但其指向的对象是可变的,所以此类必须安全发布,即不能对外提供可以修改final对象的接口。
7、JAVA怎么避免死锁?
1、加锁顺序
当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。
2、加锁时限
在尝试获取锁的时候加一个超时时间,这也就意味着在 尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行。
3、死锁检测
死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。
除此之外,每当有线程请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。
那么当检测出死锁时,这些线程该做些什么呢?一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。
这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。
虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁。一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同 一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。
8、数据库为什么建立索引?
优点:
第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
缺点:
第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
适合应用索引的:经常需要搜索的列上,经常需要范围查询的,主键等。
不适合引用索引的:经常不用来查询的,大字段的比如text段等。
9、硬盘里一个50G大小的文件和另一个100G文件,里面存储着不同的名字,如何在一个内存很小的电脑上实现两个文件的交集运算。
方法一:先用哈希切分,再用分桶+组内Hash索引的方法
将一个大文件里的数据使用一个哈希函数进行切分为许多小的文件,这样相同的数据一定会进入同一个文件当中去,并进行文件编号。
对另外一个文件也是用相同的哈希函数进行切分为相同数目的小文件,这样我们只需要将相同编号里的文件进行比较。这样其时间复杂度就会降低为 O(n)。
相同的文件查找时可以先对一个文件建立hash索引(桶+链表),然后对另一个文件依次按照索引进行查找。若hash值相同在进行进一步比较即可。
方法二:位图方法 O(n)
这有个前提是文件中必须存储的是数字。那么根据位图,我们可以将第一个文件中所有数据映射到位图中去。
然后再不断导入第二个文件,如果发现某个数字已经存储在位图中,就说明这是两个文件的交集。
方法三:近似解-布隆过滤器 O(n)
将A文件每个数据经过多个Hash函数映射到一个位图上,然后第二个文件同 样的做法,如果全部命中,说明相同。否则说明不存在。但是这个有一定的错误率。
方法四:多路归并排序 Onlog(n)+O(n)
先将文件划分为很多等量的小文件。然后对每个小文件导入内存进行内部排 序。这样就有了很多有序的小文件。
然后对很多有序的小文件进行多路归并排序,然后不断写入大文件即可。(Onlog(n))最终就得到了一个有序的大文件。最后对两个有序的大文件进行查找相同的值即可(O(n))。