记2017.3.21阿里面试经历,java方向

来源:互联网 发布:医院耗材管理软件 源码 编辑:程序博客网 时间:2024/04/29 19:04

1. Java有什么新特性

  • Java语言
  • 编译器
  • 类库
  • 工具
  • Java运行时

1. 1 Java语言

  • Lambda表达式(闭包)允许把函数作为一个方法的参数,或者把代码看成数据。
Arrays.asList("a","b","d").forEach(e->System.out.println(e));Arrays.asList( "a", "b", "d" ).forEach( e -> {    System.out.print( e );    System.out.print( e );} );Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
  • 增加函数式接口,可以被隐式转换为lambda表达式@FunctionInterface,默认方法与静态方法并不影响函数式接口的契约。
  • 接口的默认方法与静态方法,所有实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)
  • 方法引用:构造器引用Class::new(无参),静态方法引用Class::static_method,特定类的任意对象Class::method,特定对象instance::method
  • 重复注解

1. 2 Java编译器的新特性

  • 参数名字,方便获取参数名字

1. 3 Java类库的新特性

  • Optional 一个容器,可以保存类型T的值,或者仅仅保存null,提供有用的方法,这样我们就不用显示进行空值检。如果Optional类的实例为非空值的话,isPresent()返回true,否则返回false,为了防止Optional为空值,orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转换,然后返回一个新的Optional实例。
  • Stream 真正的函数式变成风格引入到Java中,极大简化了集合框架的处理。
  • Date/Time API  Clock类,指定时区,获得当前时刻,日期,时间
  • parrellelSort()方法,在多核机器上极大提高数组排序速度。
Optional< String > fullName = Optional.ofNullable( null );System.out.println( "Full Name is set? " + fullName.isPresent() );        System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

2. synchronized如何实现

  • 对象头 Mark
锁就保存在对象头中,对象头分为两部分信息,第一部分用于存储对象自身运行时的数据,如HashCode,GC分代年龄等,另一部分用于存储指向方法区类型数据的指针。
  • 偏向锁
偏向锁实际上是一种优化锁,其目的是为了减少数据在无竞争情况下的性能损耗。其核心思想就是锁会偏向第一个获取它的线程,在接下来的执行过程中该锁没有其他的线程获取,则持有偏向锁的线程永远不需要同步。
  • 轻量级锁
在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能损耗。
  • 自旋锁
sychronized锁是一种重量级锁,在互斥状态下,没有得到锁的线程会被挂起阻塞,而挂起线程和恢复线程的操作都需要转入内核态中完成。所谓自旋锁,就是让没有获得锁的进程自己运行一段时间自循环(默认开启),但是不挂起线程的代价就是该线程会一直占用处理器,如果锁占用的时间很短,自旋等待的效果很好,反之,自旋锁会消耗大量处理器资源,因此,自旋的等待时间必须有一定限度,超过限度还没有获得锁,就要挂起线程。
  • 锁的升级过程
为了减少获得锁和释放锁带来的性能消耗,这几个状态会随着竞争情况逐渐升级,状态依次是无状态锁、偏向锁、轻量级锁、自旋锁和重量级锁。锁只能升级不能降级。
  • synchronized用法:
修饰方法和代码块:某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码,进行了同步的方法和没有进行同步的方法是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法,两个被加锁的对象方法具有互斥性。同时修饰静态方法和实例方法,却可以交替进行互不干扰。
  • synchronized的缺陷:
当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,对高并发系统是致命的。如果某个线程在同步方法里面发生了死循环,那么永远不会释放对象锁,其他线程就要永远等待。
synchronized块具有优势,同步操作的内容可以与同步对象无关。
  • 形象的比喻

打个比方:一个object就像一个大房子,大门永远打开。房子里有 很多房间(也就是方法)。

这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。

另外我把所有想调用该对象方法的线程比喻成想进入这房子某个 房间的人。所有的东西就这么多了,下面我们看看这些东西之间如何作用的。

在此我们先来明确一下我们的前提条件。该对象至少有一个synchronized方法,否则这个key还有啥意义。当然也就不会有我们的这个主题了。

一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的 房间)。于是他走上去拿到了钥匙,并且按照自己 的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。

因此,普通情况下钥匙的使用原则是:“随用随借,用完即还。”

这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。

要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。象前面例子里那个想连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能再次拿到。 

再来看看同步代码块。和同步方法有小小的不同。

1.从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间。

2.同步代码块还可以人为的指定获得某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你可以用本房的钥匙;你也可以指定用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。

         记住你获得的那另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。

         为什么要使用同步代码块呢?我想应该是这样的:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,再对这些变量做一些 操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围。


3. 事务介绍

http://blog.csdn.net/zhanghaor/article/details/57084350

事务的四个属性:持久性、原子性、隔离性、一致性。其中一致性是最基本的属性,其他三个属性都是为了保证一致性二存在的。所谓一致性是指数据处于一种有意义的状态,这种状态是语义上的而不是语法上的。例如转账过程中的一致性。
在数据库实现场景中,一致性可以分为数据库外部一致性和数据库内部一致性。前者由外部应用的编码来保证,即某个应用在执行转账的数据库操作时,必须在同一个事务内部调用对账户A和账户B的操作,㐊数据库本身能解决的。后者有数据库来保证,即在同一个事务内部的一组操作必须全部执行成功,这是事务处理的原子性。
为了实现原子性,需要通过日志,将所有对数据库的更新操作都写入日志,如果一个事务中的一部分操作成功,但以后的操作无法继续,则通过回溯日志,将已经操作成功的操作撤销,从而达到全部操作失败的目的。典型场景是,数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程,读取日志进行redo(重演将所有已经执行但未成功写入到磁盘的操作,保证持久性),再对所有到崩溃时尚未成功提交的事务进行undo(撤销所有执行了一部分但尚未提交的操作,保证原子性)。crash recovery结束后,数据库恢复到一致性状态。
在多个事务并行进行的情况下,即时保证了每一个事务的原子性,仍然可能导致数据的不一致的结果。对此引入了隔离性,即保证每一个事务能够看到的数据总是一致的,好像其他并发事务并不存在一样。实现隔离性有两种典型锁:
一种是悲观锁,即当前事务将所有涉及操w作的对象加锁,操作完成后释放给其他对象使用。为了尽可能提高性能。发明了各种粒度,各种性质的锁,为了解决死锁问题,又发明了两阶段锁协议等一些列技术。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。

4. Redis与关系型数据库的同步问题

Redis是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key-value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python,Ruby,Erlang,PHP客户端,使用很方便。
按照我们一般的使用Redis的场景应该是这样的:

也就是说:我们会先去redis中判断数据是否存在,如果存在,则直接返回缓存好的数据。而如果不存在的话,就会去数据库中,读取数据,并把数据缓存到Redis中。适用场合:如果数据量比较大,但不是经常更新的情况(比如用户排行)

而第二种Redis的使用,跟第一种的情况完成不同,具体的情况请看:


这里我们会先去Redis中判断数据是否存在,如果存在,则直接更新对应的数据(吧对应更新过的key记录下来,比如也保存到redis中,key为save_update_keys),并把更新后的数据返回给页面,而如果不保存的话,就会去先更新数据库中的内容,然后把数据保存一份到Redis。后面的工作,后台会有相关机制把Redis中的save_update_keys存储的key,分别读取出来,找到对应的数据,更新到DB中。优点:主要目的是把Redis当做数据库使用,更新获取数据比DB块,非常适合大数据量的频繁变动(比如微博),缺点,对Redis的依赖很大,要做好宕机时的数据保存。

5. 数据库索引

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询,更新数据库表中数据,索引的实现通常使用B树(所有节点的平衡因子均为0的多叉查找树)及其变种B+树。

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。一是增加了数据库的存储空间二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)


上图展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。


创建索引可以大大提高系统的性能。

第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 


也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?因为,增加索引也有许多不利的方面。

第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。


一般说来,应该在这些列上建立索引:

在经常需要搜索的列上,可以加快搜索的速度,

在作为主键的列上,强制该列的唯一性和组织表中的排列结构

在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度

在需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的

在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询的时间

在经常使用where子句中的列上面创建索引,加快条件的判断速度。



4 0
原创粉丝点击