java面试题(上)
- 初始标记:这个过程只是标记以下GC Roots能够直接关联的对象,但是仍然会Stop The World;
- 并发标记:进行GC Roots Tracing的过程,可以和用户线程一起工作。
- 重新标记:用于修正并发标记期间由于用户程序继续运行而导致标记产生变动的那部分记录,这个过程会暂停所有线程,但其停顿时间远比并发标记的时间短;
- 并发清理:可以和用户线程一起工作。
- 对CPU资源比较敏感,在并发阶段,虽然不会导致用户线程停顿,但是会占用一部分线程资源,降低系统的总吞吐量。
- 无法处理浮动垃圾,在并发清理阶段,用户线程的运行依然会产生新的垃圾对象,这部分垃圾只能在下一次GC时收集。
- CMS是基于标记-清除算法实现的,意味着收集结束后会造成大量的内存碎片,可能导致出现老年代剩余空间很大,却无法找到足够大的连续空间分配当前对象,不得不提前触发一次Full GC。
- 并行与并发:充分利用多CPU来缩短Stop The World的停顿时间;
- 分代收集:不需要其他收集配合就可以管理整个Java堆,采用不同的方式处理新建的对象、已经存活一段时间和经历过多次GC的对象获取更好的收集效果;
- 空间整合:与CMS的”标记-清除”算法不同,G1在运行期间不会产生内存空间碎片,有利于应用的长时间运行,且分配大对象时,不会导致由于无法申请到足够大的连续内存而提前触发一次Full GC;
- 停顿预测:G1中可以建立可预测的停顿时间模型,能让使用者明确指定在M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
ThreadPoolExecutor mExecutor = new ThreadPoolExecutor( corePoolSize,// 核心线程数 maximumPoolSize, // 最大线程数 keepAliveTime, // 闲置线程存活时间 TimeUnit.MILLISECONDS,// 时间单位 new LinkedBlockingDeque<Runnable>(),// 线程队列 Executors.defaultThreadFactory(),// 线程工厂 new AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略 );
- 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
- 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
- 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
- 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
- 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
- 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
- 直接运行:Spring Boot可以建立独立的Spring应用程序;
- 简化部署:内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了。
- 简化配置:POM,提供的POM可以简化Maven的配置;XML,无需再像Spring那样搞一堆繁琐的xml文件的配置;
- 可以自动配置Spring;
- 提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
- 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
- 其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
- 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
此题,在我之前的一篇文章算法里头有所提到,当时给出的方案是:IP的数目还是有限的,最多2^32个,所以可以考虑使用hash将ip直接存入内存,然后进行统计。
再详细介绍下此方案:首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。
使用哈希加盐法来为密码加密
最简单、常见的破解方式当属字典破解(Dictionary Attack)和暴力破解(Brute Force Attack)方式。这两种方法说白了就是猜密码。
字典破解和暴力破解都是效率比较低的破解方式。如果你知道了数据库中密码的哈希值,你就可以采用一种更高效的破解方式,查表法(Lookup Tables)。还有一些方法,比如逆向查表法(Reverse Look up Tables)、彩虹表(Rainbow Tables)等,都和查表法大同小异。现在我们来看一下查表法的原理。
查表法不像字典破解和暴力破解那样猜密码,它首先将一些比较常用的密码的哈希值算好,然后建立一张表,当然密码越多,这张表就越大。当你知道某个密码的哈希值时,你只需要在你建立好的表中查找 该哈希值,如果找到了,你就知道对应的密码了。
从上面的查表法可以看出,即便是将原始密码加密后的哈希值存储在数据库中依然是不够安全的。那么有什么好的办法来解决这个问题呢?答案是加盐。
盐(Salt)是什么?就是一个随机生成的字符串。我们将盐与原始密码连接(concat)在一起(放在前面或后面都可以),然后将concat后的字符串加密。采用这种方式加密密码,查表法就不灵了(因为盐是随机生成的)。
单单使用哈希函数来为密码加密是不够的,需要为密码加盐来提高安全性,盐的长度不能过短,并且盐的产生应该是随机的。
- HashMap 的基本组成成员
- /**
- * The table, resized as necessary. Length MUST Always be a power of two.
- */
- transient Entry[] table;
- static class Entry<K,V> implements Map.Entry<K,V> {
- final K key;
- V value;
- Entry<K,V> next;
- final int hash;
- ……
- }
- put 方法的具体实现
- public V put(K key, V value) {
- // HashMap允许存放null键和null值。
- // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
- if (key == null)
- return putForNullKey(value);
- // 根据key的keyCode重新计算hash值。
- int hash = hash(key.hashCode());
- // 搜索指定hash值在对应table中的索引。
- int i = indexFor(hash, table.length);
- // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- // 如果i索引处的Entry为null,表明此处还没有Entry。
- modCount++;
- // 将key、value添加到i索引处。
- addEntry(hash, key, value, i);
- return null;
- }
- remove 方法的具体实现。
- 为什么时HashMap的容量总是2的n次方?
- 其他一些基本方法的基本介绍
- 什么是红黑树
1、基于连接与无连接
2、TCP要求系统资源较多,UDP较少;
3、UDP程序结构较简单
4、流模式(TCP)与数据报模式(UDP);
5、TCP保证数据正确性,UDP可能丢包
6、TCP保证数据顺序,UDP不保证
简单说,让双方都证实对方能发收。
知道对方能收是因为收到对方的因为收到而发的回应。
具体:
1:A发,B收, B知道A能发
2:B发,A收, A知道B能发收
3:A发,B收, B知道A能收
一个线程从被提交(submit)到执行共经历以下流程:
- 线程池判断核心线程池里是的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程
- 线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务储存在这个工作队列里。如果工作队列满了,则进入下一个流程。
- 线程池判断其内部线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已满了,则交给饱和策略来处理这个任务。
任务拒接策略?
有4种内置的实现策略和一个用户自定义拒绝策略。
AbortPolicy 为java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。
DiscardPolicy 直接抛弃,任务不执行,空方法 。
DiscardOldestPolicy 从队 列里面抛弃head的一个任务,并再次execute 此task。
CallerRunsPolicy 在调用execute的线程里面执行此command,会阻塞入口 。
用户自定义拒绝策略 实现RejectedExecutionHandler,并自己定义策略模式。
再次需要注意的是,ThreadPoolExecutor.submit() 函数,此方法内部调用的execute方法,并把execute执行完后的结果给返回,但如果任务并没有执行的话(被拒绝了),则submit返回的future.get()会一直等到。
future 内部其实还是一个runnable,并把command给封装了下,当command执行完后,future会返回一个值。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 (LinkedList是双向链表,有next也有previous)
2、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3、对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
HashMap的key和value都允许为空,treeMap的value允许为空。
- 有几种实现方式 ?
5种
1.饿汉模式(调用效率高,但是不能延时加载):
2.懒汉模式(调用效率不高,但是能延时加载):
3.双重检测锁模式(由于JVM底层模型原因,偶尔会出问题,不建议使用):
4.静态内部类式(线程安全,调用效率高,可以延时加载):
5.枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用):
如何选用:
-单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉
-单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式
- 单例模线程安全吗?
不安全 。
- 一般如何保证它线程安全 ?
double-check 双重检查锁定 。
- 修饰符 volatile 有什么作用 ?
能保证被它修饰的成员变量可以被多个线程正确的处理。Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小
不是有序的.
- 有没有有顺序的Map实现类?
有TreeMap和LinkedHashMap。
- TreeMap和LinkedHashMap是如何保证它的顺序的?
LinkedHashMap 是根据元素增加或者访问的先后顺序进行排序,而 TreeMap是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)。
- 哪个的有序实现比较好?
TreeMap TreeMap则实现了 SortedMap 接口。
- 你觉得还有没有比它更好或者更高效的实现方式?
参照TreeMap的value排序,我们一样的也可以实现HashMap的排序。
栅栏(Java的并发包中的CyclicBarrier) CountDownLatch CyclicBarrier Semaphore
- CountDownLatch (N个线程数量count减为0 主程序或一组程序开始执行)
CountDownLatch是一个计数器闭锁,主要的功能就是通过await()方法来阻塞住当前线程,然后等待计数器减少到0了,再唤起这些线程继续执行。
这个类里主要有两个方法,一个是向下减计数器的方法:countdown(),如果取得当前的状态为0,说明这个锁已经结束,直接返回false;
如果没有结束,然后去设置计数器减1,如果compareAndSetState不成功,则继续循环执行。 而其中的一直等待计数器归零的方法是await()。
- CyclicBarrier(N个线程,他们之间任何一个没有完成,所有的线程都必须等待)
CyclicBarrier是加计数方式,计数达到构造方法中参数指定的值时释放所有等待的线程。
- Semaphore(Semaphore 是只允许一定数量的线程同时执行一段任务。)
你知道它的实现原理吗?
继续问,你还知道其它的实现方式吗?
继续问,你觉得这些方式里哪个方式更好?
如果让你来写的话,你觉得还有比它更好的实现方式吗?
- NIO模型 其中的selector 职责和实现原理
传统的socket IO中,需要为每个连接创建一个线程,当并发的连接数量非常巨大时,线程所占用的栈内存和CPU线程切换的开销将非常巨大。使用NIO,不再需要为每个线程创建单独的线程,可以用一个含有限数量线程的线程池,甚至一个线程来为任意数量的连接服务。由于线程数量小于连接数量,所以每个线程进行IO操作时就不能阻塞,如果阻塞的话,有些连接就得不到处理,NIO提供了这种非阻塞的能力。
1、增加了一个角色,要有一个专门负责收集客人需求的人。NIO里对应的就是Selector。
2、由阻塞服务方式改为非阻塞服务了,客人吃着的时候服务员不用一直侯在客人旁边了。传统的IO操作,比如read(),当没有数据可读的时候,线程一直阻塞被占用,直到数据到来。NIO中没有数据可读时,read()会立即返回0,线程不会阻塞。
NIO中,客户端创建一个连接后,先要将连接注册到Selector,相当于客人进入餐厅后,告诉前台你要用餐,前台会告诉你你的桌号是几号,然后你就可能到那张桌子坐下了,SelectionKey就是桌号。当某一桌需要服务时,前台就记录哪一桌需要什么服务,比如1号桌要点菜,2号桌要结帐,服务员从前台取一条记录,根据记录提供服务,完了再来取下一条。这样服务的时间就被最有效的利用起来了。
- NIO的核心是什么 (Selector)
Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。
程序计数器
|
指示当前程序执行到了哪一行,执行JAVA方法时纪录正在执行的虚拟机字节码指令地址;执行本地方法时,计数器值为undefined
|
虚拟机栈
|
用于执行JAVA方法。栈帧存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。程序执行时栈帧入栈;执行完成后栈帧出栈
|
本地方法栈
|
用于执行本地方法,其它和虚拟机栈类似
|
着重说一下虚拟机栈中的局部变量表,里面存放了三个信息:
- 各种基本数据类型(boolean、byte、char、short、int、float、long、double)
- 对象引用(reference)
- returnAddress地址
- 为什么新生代要分出两个survivor区?
GC名称
|
介绍
|
Minor GC
|
发生在新生代,频率高,速度快(大部分对象活不过一次Minor GC)
|
Major GC
|
发生在老年代,速度慢
|
Full GC
|
清理整个堆空间
|
- java中垃圾回收机制?
- 垃圾回收算法概述
把堆均分成两个大小相同的区域,只使用其中的一个区域,直到该区域消耗完。此时垃圾回收器终端程序的执行,通过遍历把所有活动的对象复制到另一个区域,复制过程中它们是紧挨着布置的,这样也可以达到消除内存碎片的目的。复制结束后程序会继续运行,直到该区域被用完。
但是,这种方法有两个缺陷:
对于指定大小的堆,需要两倍大小的内存空间,
需要中断正在执行的程序,降低了执行效率
- 垃圾回收器有哪些?
- 减少new对象。每次new对象之后,都要开辟新的内存空间。这些对象不被引用之后,还要回收掉。因此,如果最大限度地合理重用对象,或者使用基本数据类型替代对象,都有助于节省内存;
- 多使用局部变量,减少使用静态变量。局部变量被创建在栈中,存取速度快。静态变量则是在堆内存;
- 避免使用finalize,该方法会给GC增添很大的负担;
- 如果是单线程,尽量使用非多线程安全的,因为线程安全来自于同步机制,同步机制会降低性能。例如,单线程程序,能使用HashMap,就不要用HashTable。同理,尽量减少使用synchronized
- 用移位符号替代乘除号。eg:a*8应该写作a<<3
- 对于经常反复使用的对象使用缓存;
- 尽量使用基本类型而不是包装类型,尽量使用一维数组而不是二维数组;
- 尽量使用final修饰符,final表示不可修改,访问效率高
- 单线程情况下(或者是针对于局部变量),字符串尽量使用StringBuilder,比StringBuffer要快;
- String为什么慢?因为String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。如果不能保证线程安全,尽量使用StringBuffer来连接字符串。这里需要注意的是,StringBuffer的默认缓存容量是16个字符,如果超过16,apend方法调用私有的expandCapacity()方法,来保证足够的缓存容量。因此,如果可以预设StringBuffer的容量,避免append再去扩展容量。如果可以保证线程安全,就是用StringBuilder。
删除元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
子 List 的元素和原 List 中的后一部分是重合的, 而子 List 还在遍历过程中时, 向原 List 中新增元素, 这样给子 List 的遍历过程造成了干扰甚至困扰, 于是就抛出了并发修改异常将会抛出java.util.ConcurrentModificationException
15、表单、AJAX提交必须执行CSRF安全过滤。
16、URL外部重定向传入的目标地址必须执行白名单过滤。
- 控制反转(Inversion of Control,英文缩写为IOC);
ioc就是典型的工厂模式,通过sessionfactory去注入实例。依赖注入 。
自己实现用什么方式? 反射原理
其实就是通过解析xml文件,通过反射创建出我们所需要的bean,再将这些bean挨个放到集合中,然后对外提供一个getBean()方法,以便我们获得这bean。
通俗来讲就如同婚姻介绍所,只需要告诉它找个什么样的女朋友,然后婚介就会按照我们的要求,提供一个mm,如果婚介给我们的人选不符合要求,我们就会抛出异常。
- 面向切面编程(Aspect Oriented Programming,英文缩写为AOP)
AOP就是典型的代理模式的体现。 实现拦截器 日志 统一权限校验 。
spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码.简单点解释,比方说你想在你的biz层所有类中都加上一个打印‘你好’的功能,这时就可以用aop思想来做.你先写个类写个类方法,方法经实现打印‘你好’,然后Ioc这个类 ref=“biz.*”让每个类都注入即可实现。
丢失更新 用户A把6改成2 用户B把2改成6 则用户A丢失了更新
脏读问题 用户A,B 看到的都是6 用户B把6改为2 则用户A读的值仍然是6
悲观 屏蔽一切违反数据操作完整性
乐观 只是在提交的时候检查是否违反数据完整性
- 复杂sql避免模糊匹配
- 索引问题 唯一索引 和普通索引
- 复杂操作
- 在可以使用UNION ALL的语句里,使用了UNION
- 字段长度小于5000用varchar,超过用TEXT,独立一张表,用主键来对应。
- 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引。
ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
也可以参考ArrayList vs. LinkedList。
- 数据库事务的几个特性:
- 数据库事务怎么保证一致性?
- 数据库隔离级别:
- synchronized和lock区别:
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
- 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
- 可重入锁与非可重入锁的区别:
AOP 全称 Aspect Oriented Programming,面向切面编程,和 OOP 一样也是一种编程思想。AOP 出现的原因是为了解决 OOP 在处理 侵入性业务上的不足。
代理模式分为静态代理和动态代理两种。
静态代理:通常用于对原有业务逻辑的扩充。创建一个代理类实现和方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。
动态代理:动态代理底层是使用反射实现的,是在程序运行期间动态的创建接口的实现。
1. 速度更快 – 红黑树
HashMap中的红黑树
HashMap中链长度大于8时采取红黑树的结构存储。
红黑树,除了添加,效率高于链表结构。
2. 代码更少 – Lambda
- Lambda表达式的基础语法:Java8引入了一个新的操作符“->”,该操作符成为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表达式中所需执行的功能,即Lambda体。
3. 强大的Stream API – Stream
一系列流水线式的中间操作。
流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
注意:
①Stream自己不会存储元素。
②Stream不会改变源对象。相反,会返回持有新结果的新Stream。
③Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
4. 便于并行 – Parallel
5. 最大化减少空指针异常 – Optional
6、ConcurrentHashMap
- Jdk1.7时隔壁级别CocnurrentLevel(锁分段机制)默认为16。
- JDK1.8采取了CAS算法
- Jdk1.8没有永久区,取而代之的是MetaSpace元空间,用的是物理内存。
a、unlock:作用于主内存,解除独占状态。
b、read:作用主内存,把一个变量的值从主内存传输到线程的工作内存。
c、load:作用于工作内存,把read操作传过来的变量值放入工作内存的变量副本中。
d、use:作用工作内存,把工作内存当中的一个变量值传给执行引擎。
e、assign:作用工作内存,把一个从执行引擎接收到的值赋值给工作内存的变量。
f、store:作用于工作内存的变量,把工作内存的一个变量的值传送到主内存中。
g、write:作用于主内存的变量,把store操作传来的变量的值放入主内存的变量中。
1、Redis有哪些数据结构?
2、使用过Redis分布式锁么,它是什么回事?
3、如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
4、假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
5、如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
6、使用过Redis做异步队列么,你是怎么用的?
7、可不可以不用sleep呢?
8、能不能生产一次消费多次呢?
9、pub/sub有什么缺点?
10、redis如何实现延时队列?
11、如果有大量的key需要设置同一时间过期,一般需要注意什么?
12、Redis如何做持久化的?
13、如果突然机器掉电会怎样?
14、bgsave的原理是什么?
15、Pipeline有什么好处,为什么要用pipeline?
16、Redis的同步机制了解么?
17、是否使用过Redis集群,集群的原理是什么?
PS:
1、指数据库事务正确执行的四个基本要素:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)2、各区域的使用
1、设计模式中提到的一个概念。2、目的指导我们如何建立一个稳定的、灵活的系统。3、开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的。(可以新增、尽可能少的修改)
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B,若预期值A和内存值V相同就把内存值修改成新值B
CAS的缺点:
1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
3.ABA问题
这是CAS机制最大的问题所在。
PS:
什么是ABA问题?
引用原书的话:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令就可能出现这种问题,在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作,在某些算法中,如果V的值首先由A变为B,再由B变为A,那么CAS将会操作成功。
- 怎么避免ABA问题?
Java中提供了java.util.concurrent.atomic中AtomicStampedReference和AtomicMarkableReference来解决ABA问题。
- 底层实现?
多CPU的情况下的cas操作是CPU提供的支持。1、这和volatile的底层实现是相同的2、底层:这个读取、对比以及设置的操作私用lock前缀保证唯一性。
方案一(先更新缓存,再更新数据库):
删除失败后,将删除key的消息发送到消息队列,重试删除直到成功(这对业务代码有侵入)
使用mysql的中间件如Canal,单启一个独立的程序去处理。
posted on 2018-12-14 10:15 ws563573095 阅读(275) 评论(0) 编辑 收藏 举报