多线程
- 创建线程的3种方式?
- 继承Thread类创建线程类
定义Thread类的子类,并重写run方法。创建Thread子类的实例。调用start()方法。
-
通过Runnable接口创建线程类
同上,只是改为定义runnable接口的实现类。
-
通过Callable和FutureTask创建线程
a) 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
b) 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
c) 使用FutureTask对象作为Thread对象的target创建并启动新线程。
d) 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
- 继承Thread类创建线程类
-
什么是线程安全?
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替运行,并且在主调试代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,则称这个类时线程安全的。线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。
-
Runnable接口和Callable接口的区别。
Runnable
只有一个run()函数,无返回值
Callable
call()函数有返回值
-
wait方法和sleep方法的区别。
sleep
sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复,调用sleep 不会释放对象锁。由于没有释放对象锁,所以不能调用里面的同步方法。
必须捕获异常
wait
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);可以调用里面的同步方法,其他线程可以访问;
-
synchronized、Lock、ReentrantLock、ReadWriteLock。
Synchronized
它可以锁住一个方法或者一段代码块,同时只有一个线程执行该方法,直到执行完或者抛出异常
Lock
规避了synchronized的缺点,它是一个接口,里面有如下四个锁方法和一个解锁方法.
使用Lock,一个线程抢占到锁资源,其他的线程可以不等待或者设置等待时间,实在抢不到可以去做其他的业务逻辑。public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); }
ReentrantLock
它实现了Lock里面的方法,需要你主动释放锁
ReadWriteLock
实现读写锁,当读取的时候线程会获得read锁,其他线程也可以获得read锁同时并发的去读取,但是写程序运行获取到write锁的时候,其他线程是不能进行操作的,因为write是排它锁.
-
介绍下CAS(无锁技术)
CAS:Compare and Swap, 翻译成 比较并交换
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
-
分布式环境下,怎么保证线程安全。
- 避免并发
- 时间戳
- 串行化
- 数据库
- 行锁
- 统一触发途径
- 什么是ThreadLocal。
ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内。将对象的作用范围限制在一个线程上下文中,使得变量的作用域为“线程级”。
线程池
-
创建线程池的4种方式。
- new CachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
-
new FixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
-
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- new CachedThreadPool
-
ThreadPoolExecutor的内部工作原理。
作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务
Executors方法提供的线程服务,都是通过参数设置来实现不同的线程池机制
future
是Runnable和Callable的调度容器。Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果(Future简介)
- FutureTask
FutureTask则是一个RunnableFuture,而RunnableFuture实现了Runnbale又实现了Futrue这两个接口。
FutureTask既是Future、Runnable,又是包装了Callable(如果是Runnable最终也会被转换为Callable ), 它是这两者的合体。
集合类

-
HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList的底层实现。
HashMap
HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分,当链表长度大于8时),当数组用完时Double!
它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap
LinkedHashMap
LinkedHashMap是HashMap的一个子类,通过维护一个运行于所有条目的双向链表,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
ConcurrentHashMap
线程安全的HashMap,将Map分割成了不同的部分,在执行更新操作时只锁住一部分。根据默认的并发级别(concurrency level),Map被分割成16个部分,并且由不同的锁控制。
Key,Value值不能为nullArrayList
以数组实现。节约空间,但数组有容量限制。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组。因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。
LinkedList
以双向链表实现。链表无容量限制,但双向链表本身使用了更多空间,每插入一个元素都要构造一个额外的Node对象,也需要额外的链表指针操作。
-
HashMap和Hashtable的区别。
Hashtable
Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。不允许键为Null
-
ArrayList、LinkedList、Vector的区别。
Vector
Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
扩容时直接Double -
HashMap和ConcurrentHashMap的区别。
线程是否安全
-
HashMap和LinkedHashMap的区别。
实现、是否支持有序存储
-
HashMap是线程安全的吗。
不是
-
ConcurrentHashMap是怎么实现线程安全的。
上面
-
其他奇奇怪怪的东西
CopyOnWriteArrayList
并发优化的ArrayList。基于不可变对象策略,在修改时先复制出一个数组快照来修改,改好了,再让内部指针指向新数组。
TreeMap
红黑树实现的Map,树的层数不会差的太远,使得所有操作的复杂度不超过 O(lgn),但也使得插入,修改时要复杂的左旋右旋来保持树的平衡。
EnumMap
在构造函数里要传入枚举类,那它就构建一个与枚举的所有值等大的数组,按Enum. ordinal()下标来访问数组。性能与内存占用俱佳。每次访问都要先对Key进行类型判断
ConcurrentSkipListMap
JDK6新增的并发优化的SortedMap,以SkipList结构实现。Concurrent包选用它是因为它支持基于CAS的无锁算法,而红黑树则没有好的无锁算法。
-
所有的Set内部基本都使用对应的Map来实现,公用一个Object
HashSet:内部是HashMap。
LinkedHashSet:内部是LinkedHashMap。
TreeSet:内部是TreeMap的SortedSet。
ConcurrentSkipListSet:内部是ConcurrentSkipListMap的并发优化的SortedSet。
CopyOnWriteArraySet:内部是CopyOnWriteArrayList的并发优化的Set,利用其addIfAbsent()方法实现元素去重,该方法的性能很一般。
- Queue
LinkedList (既是List也Queue)
ArrayDeque
以循环数组实现的双向Queue。大小是2的倍数,默认是16。空间用完时Double!
PriorityQueue
用平衡二叉最小堆实现的优先级队列,不再是FIFO,而是按元素实现的Comparable接口或传入Comparator的比较结果来出队,数值越小,优先级越高,越先出队。
ConcurrentLinkedQueue/Deque (线程安全)
无界的并发优化的Queue,基于链表,实现了依赖于CAS的无锁算法。
-
线程安全的阻塞队列
BlockingQueue,一来如果队列已空不用重复的查看是否有新数据而会阻塞在那里,二来队列的长度受限,用以保证生产者与消费者的速度不会相差太远。
ArrayBlockingQueue
定长的并发优化的BlockingQueue,也是基于循环数组实现。有一把公共的锁与notFull、notEmpty两个Condition管理队列满或空时的阻塞状态。
LinkedBlockingQueue/Deque
可选定长的并发优化的BlockingQueue,基于链表实现,所以可以把长度设为Integer.MAX_VALUE成为无界无等待的。
PriorityBlockingQueue
无界的PriorityQueue,也是基于数组存储的二叉堆(见前)。一把公共的锁实现线程安全。因为无界,空间不够时会自动扩容,所以入列时不会锁,出列为空时才会锁
DelayQueue
内部包含一个PriorityQueue,同样是无界的,同样是出列时才会锁。一把公共的锁实现线程安全。元素需实现Delayed接口,每次调用时需返回当前离触发时间还有多久,小于0表示该触发了。
-
同步队列
SynchronousQueue
同步队列本身无容量,放入元素时,必须等待元素被另一条线程的消费者取走再返回。JDK线程池里用它。
参考资料:http://calvin1978.blogcn.com/articles/collection.html
JVM
- 类加载的过程。
加载
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生存一个代表这个类的java.lang.Class对象,作为方法区这个类
的各种数据的访问入口。
验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
- 为类变量分配内存并设置类变量初始值
注意对于静态变 量设置初始值不是设置类变量被赋予的值,基本数据类型为对应的零值,引用类 型为null;对于常量(即带有final修饰符的静态变量)则会直接设置其被赋予的 值。
解析
将常量池内的符号引用替换为直接引用的过程
- 符号引用:符号引用以一组符号来描述所引用的模板,符号可以是任何形 式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机 实现的内存布局无关。
- 直接引用:直接饮用可以是直接指向目标的指针,相对偏移量或是一个能 间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的。
初始化
执行类构造器()方法的过程
- ()方法由所有类变量的赋值动作和静态语句块合并产生的, 顺序由语句在源文件中出现的顺序所决定的,静态语句块只能访问 在静态语句块之前的变量,定义在后面的变量,静态语句块内可以 赋值但不能访问。
- 虚拟机会保证先调用父类的()方法,所以在虚拟机中第一个 被执行的()方法的类是java.lang.Object,也意味着父类中 定义的静态语句块要优先于子类。
- 接口也可能会生成()方法,因为接口可能存在变量初始化的 赋值操作。接口的()方法不需要先执行父接口的() 方法,只有父接口中定义的变量被使用时,父接口才会初始化。
- ()方法对于类或接口不是必需的,如果他们没有静态语句块 和静态变量的赋值操作,那么编译器可以不为他们生成()方 法。
- 虚拟机会保证()方法是线程安全的。
使用
卸载
- 双亲委派模型。
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
-
有哪些类加载器。
Bootstrap ClassLoader
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
App ClassLoader
负责记载classpath中指定的jar包及目录中class
Custom ClassLoader
属于应用程序根据自身需要自定义的ClassLoader
-
能不能自己写一个类叫java.lang.String。
不能自己写以”java.”开头的类,其要末不能加载进内存,即便你用自定义的类加载器去强行加载,也会收到1个SecurityException
内存
-
运行时数据区域
程序计数器
线程私有,负责指令跳转,执行Java方法时记录虚拟机字节 码指令地址,执行Native方法时为空。
虚拟机栈
- 线程私有,存储栈帧。
- 栈帧:方法运行时的基础数据结构,方法执行时创建,用于存储局 部变量表,操作数栈,动态链接,方法出口等信息。
- 局部变量表:由若干个局部变量空间(Slot)组成,存放编译器可知的基本数据类型,对象的引用和返回地址类型,其中64位整数long 和浮点数double的数据会占用2个空间(Slot),其余数据类型只占用一个。局部变量表的内存空间是在编译时完成分配的,在方法运行期间不会发生改变。
> 本地方法栈
规范中无强制规定,在HotspotVM中与虚拟机栈合二为一。
堆
线程共享,虚拟机启动时创建,存放对象实例。也叫GC堆。
方法区
线程共享,存放虚拟机加载的类,常量,静态变量,JIT编译后的代码等数据。
运行时常量池
方法区的一部分,存放编译期生成的各种字面量和符号引用。
直接内存
不属于运行时数据区域,通过NIO类通过Native函数库直接分配堆外内存。
-
运行时内存溢出异常
- 程序计数器:无
- 虚拟机栈:
- StackOverflowError: 线程请求的栈深度大于虚拟机所允许的最大深度。
- OutOfMemoryError:如果虚拟机栈可以动态扩展,而线程无法申 请到足够的内存。
- 本地方法栈:与虚拟机栈相同。
- 堆:
- OutOfMemoryError:如果堆中没有足够的内存来完成实例分配,并且堆也无法进行扩展。
- 方法区:
- OutOfMemoryError:如果方法区中没有足够的内存来完成实例分配,并且方法区也无法进行扩展。
- 运行时常量池:与方法区相同。
- 直接内存:
- OutOfMemoryError:动态扩展时,超出了物理内存或操作系统的 限制。
GC
- 介绍下垃圾收集机制(在什么时候,对什么,做了什么)。
Minor GC
当Eden满时对其进行GC,删除已废弃对象,并将依然存活的对象移动到Survivor区
当Survivor中的对象经历过15次GC依旧没有被回收时,进入老生代
Full GC
发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次Minor GC。Major GC 的速度一般会比Minor GC慢10倍以上。
-
垃圾收集有哪些算法,各自的特点。
“标记-清除”算法
首先标记出所有需要回收的对象,在标记完成后统一回 收所有被标记的对象。缺点:效率低;产生大量内存碎片。
“复制”算法
将可用内存划分为大小相等的两块,每次只使用其中的一 块,当进行垃圾回收时,将存活的对象复制到另一块上,再把原来的空间 一次清理掉。优点:实现简单,运行高效。缺点:将内存缩小了一半,代 价高昂;存活率较高时复制操作增加,效率降低。
“标记-整理”算法
与“标记-清除”算法一样,都是先标记出所有需要回收的对象,但后续不是直接清理可回收的对象,而是让存活的对象向一端移动,然后清理掉端边界以外的内存。
分代收集算法
针对对象存活周期的不同将内存划分为若干块,对每一块采用合适的算法去进行垃圾回收。如新生代的存活率较低,则采用复制算法;老年代存活率较高,且没有额外内存提供分配担保,必须采用“标记-清除”或“标记-整理”算法。
算法
快排
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
- 选取数据列中的一个值K(通常是第一个)
- 从后往前找到第一个比K小的数与K交换
- 从前往后找到第一个比K大的数字与K交换
- 重复2,3直到相遇
- 从相遇点将数据列分为两个子列,并重复全过程直至列不可再分
堆排
利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。
- 首先根据该数组元素构造一个完全二叉树
- 将该树调整为大(小)顶树
- 将第一个元素与最后一个元素交换
- 将除最后一个元素的其他元素执行方法2,重复直到所有元素有序
树
手写红黑树就可以跑路了
Mysql
- Mysql索引的数据结构。
B-Tree
首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到null指针,前者查找成功,后者查找失败。
B+Tree
与B-Tree相比,B+Tree有以下不同点:
- 每个节点的指针上限为2d而不是2d+1。
- 内节点不存储data,只存储key;叶子节点不存储指针。
带有顺序访问指针的B+Tree
可以方便的根据指向下一个node的指针获得范围数据
- 为什么使用B-Tree(B+Tree)
索引往往以索引文件的形式存储的磁盘上。索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。
- MyISAM 与 InnoDB 索引实现
- MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。
- InnoDB也使用B+Tree作为索引结构,InnoDB的数据文件本身就是索引文件。树的叶节点data域保存了完整的数据记录。InnoDB的辅助索引data域存储相应记录主键的值而不是地址。
额外的故事
- BTree
就是二叉搜索树
- B*Tree
在B+Tree的基础上,相邻非叶子节点也带有一个指向Next同层节点的指针。在插入时如果一个节点已满但是下一个兄弟节点未满,则会将部分数据移给兄弟节点。理论上效率比B+Tree高
-
SQL怎么进行优化。
- 保证不查询多余的列与行
- 慎用distinct关键字
- 慎用union关键字
- 判断表中是否存在数据
- 连接查询的优化:减少连接表的数据数量可以提高效率
- insert into select批量插入,明显提升效率
- 根据实际情况(CPU or I/O)
- SQL关键字的执行顺序。
- FROM:对FROM子句中前两个表执行笛卡尔积生成虚拟表vt2
- ON: 对vt1表应用ON筛选器只有满足 join_condition 为真的行才被插入vt2
- OUTER(join):如果指定了 OUTER JOIN保留表(preserved table)中未找到的行将行作为外部行添加到vt2,生成t3,如果from包含两个以上表,则对上一个联结生成的结果表和下一个表重复执行步骤和步骤直接结束。
- WHERE:对vt3应用 WHERE 筛选器只有使 where_condition 为true的行才被插入vt4
- GROUP BY:按GROUP BY子句中的列列表对vt4中的行分组生成vt5
- CUBE|ROLLUP:把超组(supergroups)插入vt6,生成vt6
- HAVING:对vt6应用HAVING筛选器只有使 having_condition 为true的组才插入vt7
- SELECT:处理select列表产生vt8
- DISTINCT:将重复的行从vt8中去除产生vt9
- ORDER BY:将vt9的行按order by子句中的列列表排序生成一个游标vc10
- TOP:从vc10的开始处选择指定数量或比例的行生成vt11 并返回调用者
- 有哪几种索引。
从数据结构角度
- B+树索引(O(log(n))):关于B+树索引,可以参考 MySQL索引背后的数据结构及算法原理
- hash索引
- 仅仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询
- 其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引
- 只有Memory存储引擎显示支持hash索引
- FULLTEXT索引(现在MyISAM和InnoDB引擎都支持了)
- R-Tree索引(用于对GIS数据类型创建SPATIAL索引)
从物理存储角度
- 聚集索引(clustered index)
- 非聚集索引(non-clustered index)
从逻辑角度
- 主键索引:主键索引是一种特殊的唯一索引,不允许有空值
- 普通索引或者单列索引
- 多列索引(复合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合
- 唯一索引或者非唯一索引
- 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。
MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建
什么时候该(不该)建索引。
- Explain包含哪些列。
- id:SELECT识别符。这是SELECT查询序列号
- select_type
- simple:简单的select,没有union和子查询
- primary:最外面的select,在有子查询的语句中,最外面的select查询就是primary
- union:union语句的第二个或者说是后面那一个
- dependent union :UNION中的第二个或后面的SELECT语句,取决于外面的查询
- union result:UNION的结果
- table:输出的行所用的表
- type:
- system:表仅有一行,这是const类型的特列,平时不会出现
- const:表最多有一个匹配行,const用于比较primary key 或者unique索引。因为只匹配一行数据,所以很快
- eq_ref:对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY
- ref:对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY
- ref_or_null:联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行。在解决子查询中经常使用该联接类型的优化。
- index_merge:使用了索引合并优化方法。在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素
- unique_subquery
- index_subquery
- range:定范围内的检索,使用一个索引来检查行
- index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小
- all:对于每个来自于先前的表的行组合,进行完整的表扫描。
- possible_keys:提示使用哪个索引会在该表中找到行
- keys:MYSQL使用的索引
- key_len:索引长度
- ref:ref列显示使用哪个列或常数与key一起从表中选择行
- rows:显示MYSQL执行查询的行数,简单且重要,数值越大越不好,说明没有用好索引
- extra:包含MySQL解决查询的详细信息
Redis
- 支持的数据类型
String
string类型是Redis最基本的数据类型,一个键最大能存储512MB
Hash
hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。每个 hash 可以存储 2^32 -1 键值对(40多亿)
List
列表是简单的字符串列表,按照插入顺序排序。
Set
string类型的无序集合。通过Hash实现
zset
string类型元素的集合,每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序
设计模式
单例
- 特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 实现方式:
- 懒汉,线程不安全;
- 懒汉,线程安全:使用synchronized对getInstance()加锁
- 饿汉:定义static变量直接new,没有达到lazy loading的效果
- 饿汉改:类static方法块中初始化
- 静态内部类:可以lazy loading
- 枚举;
- 双重校验锁;
观察者
- 又称为发布/订阅(Publish/Subscribe)模式
- 订阅者可以通过订阅来获得发布者消息更新的提醒
Spring
-
Hibernate和Mybatis的区别。
- Hibernate功能强大,数据库无关性好,O/R映射能力强,如果你对Hibernate相当精通,而且对Hibernate进行了适当的封装,那么你的项目整个持久层代码会相当简单,需要写的代码很少,开发速度很快,非常爽。
- Hibernate的缺点就是学习门槛不低,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用好Hibernate方面需要你的经验和能力都很强才行。
- iBATIS入门简单,即学即用,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。
- iBATIS的缺点就是框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。
- Spring MVC和Struts2的区别。
- Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文.
- SpringMVC的方法之间基本上独立的,独享request response数据,Struts2所有Action变量是共享的,每次来了请求就创建一个Action,一个Action对象对应一个request上下文。
- Struts2把request,session等servlet生命周期的变量封装成一个一个Map,供给每个Action使用,并保证线程安全,比较耗费内存
- 拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式
- SpringMVC的入口是servlet,而Struts2是filter
- SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去
- SpringMVC验证支持JSR303,处理起来相对更加灵活方便,而Struts2验证比较繁琐,感觉太烦乱
- Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高
- Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展
- SpringMVC开发效率和性能高于Struts2。SpringMVC可以认为已经100%零配置。
- Spring用了哪些设计模式。
- 简单工厂
- 工厂方法(Factory Method)
- 单例模式(Singleton)
- 适配器(Adapter)
- 包装器(Decorator)
- 代理(Proxy)
- 观察者(Observer)
- 策略(Strategy)
- 模板方法(Template Method)
- Spring中AOP主要用来做什么。
- 拦截器(权限管理等)
- 性能测试???
- Spring注入bean的方式。
- 使用属性的setter方法注入 这是最常用的方式;
- 使用构造器注入;
- 使用Filed注入(用于注解方式).
- Spring装配bean的方式
- 在XML中进行显式配置
- 在Java中进行显式配置
- 隐式的bean发现机制和自动装配
- 什么是IOC,什么是依赖注入。
- IOC inversion of control 控制反转
- DI Dependency Injection 依赖注入
- 依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源
- Spring是单例还是多例,怎么修改。
Controller默认单例,通过注解@Scope(“prototype”)更改为多例
-
spring bean作用域
singleton:单例模式
当spring创建applicationContext容器的时候,spring会欲初始化所有的该作用域实例,加上lazy-init就可以避免预处理;
prototype:原型模式
每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理;
request:
每次请求都新产生一个实例,和prototyp搜索e不同就是创建后,接下来的管理,spring依然在监听
session:
每次会话
global session:全局的web域
类似于servlet中的application
-
介绍下Mybatis/Hibernate的缓存机制。
Hibernate
- Hibernate一级缓存是Session缓存,利用好一级缓存就需要对Session的生命周期进行管理好。建议在一个Action操作中使用一个Session。一级缓存需要对Session进行严格管理。
-
Hibernate二级缓存是SessionFactory级的缓存。称为进程级缓存或SessionFactory级缓存,它可以被所有session共享,它的生命周期伴随着SessionFactory的生命周期存在和消亡。
- 内置缓存中存放的是SessionFactory对象的一些集合属性包含的数据(映射元素据及预定SQL语句等),对于应用程序来说,它是只读的。
- 外置缓存中存放的是数据库数据的副本,其作用和一级缓存类似.二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备。
Mybatis
- MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。
-
默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环 依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:
相同点
- Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
不同点
- Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。
-
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。
两者比较
- 因为Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。
-
而MyBatis在这一方面,使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据的出现会给系统的正常运行带来很大的隐患。
Spring事务隔离级别和传播性。
-
事务的7种传播级别:
PROPAGATION_REQUIRED
默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。
PROPAGATION_SUPPORTS
从字面意思就知道,supports,支持,该传播级别的特点是,如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作。应用场景较少。
PROPAGATION_MANDATORY
该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。
PROPAGATION_REQUIRES_NEW
从字面即可知道,new,每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
PROPAGATION_NOT_SUPPORTED
这个也可以从字面得知,not supported ,不支持,当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
PROPAGATION_NEVER
该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!这个级别上辈子跟事务有仇。
PROPAGATION_NESTED
字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
-
什么是嵌套事务
嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。
-
如果子事务回滚,会发生什么?
父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。
-
如果父事务回滚,会发生什么?
父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。那么:
-
事务的提交,是什么情况?
是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交
-
数据隔离级别分为不同的四种:
Serializable
最严格的级别,事务串行执行,资源消耗最大;
REPEATABLE READ
保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
READ COMMITTED
大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”.该级别适用于大多数系统。
Read Uncommitted
保证了读取过程中不会读取到非法数据。
其他
-
介绍下栈和队列。
- 不会的重修数据结构去
- IO和NIO的区别。
IO | NIO |
---|---|
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
-
接口和抽象类的区别。
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 抽象类要被子类继承,接口要被类实现。
- 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
- 抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
- 抽象类里可以没有抽象方法
- 如果一个类里有抽象方法,那么这个类只能是抽象类
- 抽象方法要被实现,所以不能是静态的,也不能是私有的。
- 接口可继承接口,并可多继承接口,但类只能单根继承。
- int和Integer的自动拆箱/装箱相关问题。
Integer与int类型的赋值
- 把int类型赋值给Integer类型。此时,int类型变量的值会自动装箱成Integer类型,然后赋给Integer类型的引用,这里底层就是通过调用valueOf()这个方法来实现所谓的装箱的。
- 把Integer类型赋值给int类型。此时,Integer类型变量的值会自动拆箱成int类型,然后赋给int类型的变量,这里底层则是通过调用intValue()方法来实现所谓的拆箱的。
Integer与int类型的比较
这里就无所谓是谁与谁比较了,Integer == int与int == Integer的效果是一样的,都会把Integer类型变量拆箱成int类型,然后进行比较,相等则返回true,否则返回false。同样,拆箱调用的还是intValue()方法。
Integer之间的比较
这个就相对简单了,直接把两个引用的值(即是存储目标数据的那个地址)进行比较就行了,不用再拆箱、装箱什么的。
int之间的比较
这个也一样,直接把两个变量的值进行比较。
-
常量池相关问题。
见《Hans JVM》 -
==和equals的区别。
- 值类型直接使用==进行值是否相等的比较
- 对象使用==比较对象引用是否为同一个
- equals理论上需要手动实现来比较想比较的东西
- 重载和重写的区别。
- 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载Overloading是一个类中多态性的一种表现。
- Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。
- 重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。
- String和StringBuilder、StringBuffer的区别。
- String:线程安全,Final,值不可修改
- StringBuilder:线程非安全的
- StringBuffer:线程安全的
- 静态变量、实例变量、局部变量线程安全吗,为什么。
静态变量
一旦静态变量被修改,其他对象均对修改可见,故线程非安全。
实例变量
单例模式(只有一个对象实例存在)线程非安全,非单例线程安全。
- 成员变量定义在类中,即类中的普通变量,在整个类中都可以被类中方法所访问(如过和局部变量重名,需用this关键字)。
- 成员变量随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的堆内存中。
- 成员变量有默认初始化值
局部变量:线程安全。
- 局部变量只定义在局部范围内,如:函数内,for循环语句内等,只在所属的区域有效。
- 局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放。
- 局部变量没有默认初始化值
- 在使用变量时需要遵循的原则为:就近原则,先找局部变量,再找实例变量(如果同名,实例变量需要用this关键字引用)
- 局部变量不能逐级重名,比如函数内定义过一个变量,就不能在for循环内定义相同的变量(两层for循环一个用i一个用j也是这个道理)
- 由于每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。
- try、catch、finally都有return语句时执行哪个。
- 不管有木有出现异常,finally块中代码都会执行;
- 当try和catch中有return时,finally仍然会执行;
- finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
- finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
- ajax的4个字母分别是什么意思
– A Asynchronous- J Javascript
- A And
- X XML
- xml全称是什么 : Extensive Mark Language
-
分布式锁的实现。
- 基于数据库实现分布式锁
- 基于缓存(redis,memcached,tair)实现分布式锁
- 基于Zookeeper实现分布式锁
- 分布式session存储解决方案。
Session Replication 方式管理 (即session复制)
- 简介:将一台机器上的Session数据广播复制到集群中其余机器上
- 使用场景:机器较少,网络流量较小
- 优点:实现简单、配置较少、当网络中有机器Down掉时不影响用户访问
- 缺点:广播式复制到其余机器有一定廷时,带来一定网络开销
Session Sticky 方式管理
- 简介:即粘性Session、当用户访问集群中某台机器后,强制指定后续所有请求均落到此机器上
- 使用场景:机器数适中、对稳定性要求不是非常苛刻
- 优点:实现简单、配置方便、没有额外网络开销
- 缺点:网络中有机器Down掉时、用户Session会丢失、容易造成单点故障
缓存集中式管理
- 简介:将Session存入分布式缓存集群中的某台机器上,当用户访问不同节点时先从缓存中拿Session信息
- 使用场景:集群中机器数多、网络环境复杂
- 优点:可靠性好
- 缺点:实现复杂、稳定性依赖于缓存的稳定性、Session信息放入缓存时要有合理的策略写入