java学习笔记记录

Java内存模型:

Java虚拟机规范中将Java运行时数据分为六种。

1.程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。

2.Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。

3.本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。

4.Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。

5.方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。

6.运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。

 

类加载器工作机制: 
1.装载:将Java二进制代码导入jvm中,生成Class文件。 
2.连接:a)校验:检查载入Class文件数据的正确性 b)准备:给类的静态变量分配存储空间 c)解析:将符号引用转成直接引用 
3:初始化:对类的静态变量,静态方法和静态代码块执行初始化工作。

双亲委派模型:类加载器收到类加载请求,首先将请求委派给父类加载器完成 
用户自定义加载器->应用程序加载器->扩展类加载器->启动类加载器。

 

java代理机制

代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。 
Java静态代理: 
代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。 
缺点:一个代理类只能代理一个业务类。如果业务类增加方法时,相应的代理类也要增加方法。 
Java动态代理: 
Java动态代理是写一个类实现InvocationHandler接口,重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写,这个公共代理类在运行的时候才能明确自己要代理的对象,同时可以实现该被代理类的方法的实现,然后在实现类方法的时候可以进行增强处理。 
实际上:代理对象的方法 = 增强处理 + 被代理对象的方法

JDK和CGLIB生成动态代理类的区别: 
JDK动态代理只能针对实现了接口的类生成代理(实例化一个类)。此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑 
CGLIB是针对类实现代理,主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法 。 
Spring AOP应用场景 
性能检测,访问控制,日志管理,事务等。 
默认的策略是如果目标类实现接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理

 

Java GC 

在什么时候:

1.新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。

2.大对象以及长期存活的对象直接进入老年区。

3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。

对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。

做什么: 
新生代:复制清理; 
老年代:标记-清除和标记-压缩算法; 
永久代:存放Java中的类和加载类的类加载器本身。

GC Roots都有哪些: 
1. 虚拟机栈中的引用的对象 
2. 方法区中静态属性引用的对象,常量引用的对象 
3. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

 

ThreadLocal(线程变量副本)

Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量。

采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。

ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。

ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。

Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。

 

Synchronized 与Lock都是可重入锁,同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁。

Synchronized是悲观锁机制,独占锁。而Locks.ReentrantLock是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。 
ReentrantLock适用场景

    1. 某个线程在等待一个锁的控制权的这段时间需要中断
    2. 需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程,锁可以绑定多个条件。
    3. 具有公平锁功能,每个到来的线程都将排队等候。

 

Volatile和Synchronized四个不同点: 


1 粒度不同,前者针对变量 ,后者锁对象和类 
2 syn阻塞,volatile线程不阻塞 
3 syn保证三大特性,volatile不保证原子性 
4 syn编译器优化,volatile不优化 
volatile具备两种特性: 
1. 保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。 
2. 禁止指令重排序优化。 
Volatile如何保证内存可见性: 
1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。 
2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

同步:就是一个任务的完成需要依赖另外一个任务,只有等待被依赖的任务完成后,依赖任务才能完成。 
异步:不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来。(异步的特点就是通知)。 
打电话和发短信来比喻同步和异步操作。 
阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作。 
非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作,等这个慢的完成后,CPU才会接着完成后续的操作。 
非阻塞会造成线程切换增加,增加CPU的使用时间能不能补偿系统的切换成本需要考虑。

 

wait()和sleep()的区别

sleep来自Thread类,和wait来自Object类

调用sleep()方法的过程中,线程不会释放对象锁。而调用 wait 方法线程会释放对象锁

sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU

sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒

 

CAS(Compare And Swap) 无锁算法: 
CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

 

 

SpringMVC运行原理 
1. 客户端请求提交到DispatcherServlet 
2. 由DispatcherServlet控制器查询HandlerMapping,找到并分发到指定的Controller中。 
4. Controller调用业务逻辑处理后,返回ModelAndView 
5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图 
6. 视图负责将结果显示到客户端

 

Spring IOC (控制反转,依赖注入)

Spring支持三种依赖注入方式,分别是属性(Setter方法)注入,构造注入和接口注入。

在Spring中,那些组成应用的主体及由Spring IOC容器所管理的对象被称之为Bean。

Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。 
简单地讲,Bean就是由Spring IOC容器初始化、装配及被管理的对象。 
获取Bean对象的过程,首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象,就可以调用他的方法。 
Spring Bean的作用域: 
Singleton:Spring IOC容器中只有一个共享的Bean实例,一般都是Singleton作用域。 
Prototype:每一个请求,会产生一个新的Bean实例。 
Request:每一次http请求会产生一个新的Bean实例。

 

servlet和Filter的区别: 
整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

Filter有如下几个用处: 
Filter可以进行对特定的url请求和相应做预处理和后处理。 
在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。 
根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。 
在HttpServletResponse到达客户端之前,拦截HttpServletResponse。 
根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

实际上Filter和Servlet极其相似,区别只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码,通过使用Filter可以实现更好的复用。

Filter和Servlet的生命周期: 
1.Filter在web服务器启动时初始化 
2.如果某个Servlet配置了 1 ,该Servlet也是在Tomcat(Servlet容器)启动时初始化。 
3.如果Servlet没有配置1 ,该Servlet不会在Tomcat启动时初始化,而是在请求到来时初始化。 
4.每次请求, Request都会被初始化,响应请求后,请求被销毁。 
5.Servlet初始化后,将不会随着请求的结束而注销。 
6.关闭Tomcat时,Servlet、Filter依次被注销。

 

HashMap与HashTable的区别。 
1、HashMap是非线程安全的,HashTable是线程安全的。 
2、HashMap的键和值都允许有null值存在,而HashTable则不行。 
3、因为线程安全的问题,HashMap效率比HashTable的要高。

HashMap的实现机制: 
1. 维护一个每个元素是一个链表的数组,而且链表中的每个节点是一个Entry[]键值对的数据结构。 
2. 实现了数组+链表的特性,查找快,插入删除也快。 
3. 对于每个key,他对应的数组索引下标是 int i = hash(key.hashcode)&(len-1); 
4. 每个新加入的节点放在链表首,然后该新加入的节点指向原链表首

 

HashMap,ConcurrentHashMap与LinkedHashMap的区别

  1. ConcurrentHashMap是使用了锁分段技术技术来保证线程安全的,锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
  2. ConcurrentHashMap 是在每个段(segment)中线程安全的
  3. LinkedHashMap维护一个双链表,可以将里面的数据按写入的顺序读出

ConcurrentHashMap应用场景

1:ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全,而同步的HashMap和HashMap的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器,只需要锁住对应的Segment就好了,所以可以保证高并发同步访问,提升了效率。

2:可以多线程写。 
ConcurrentHashMap把HashMap分成若干个Segmenet 
1.get时,不加锁,先定位到segment然后在找到头结点进行读取操作。而value是volatile变量,所以可以保证在竞争条件时保证读取最新的值,如果读到的value是null,则可能正在修改,那么就调用ReadValueUnderLock函数,加锁保证读到的数据是正确的。 
2.Put时会加锁,一律添加到hash链的头部。 
3.Remove时也会加锁,由于next是final类型不可改变,所以必须把删除的节点之前的节点都复制一遍。 
4.ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对Hash表的不同Segment进行的修改。

ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全,而同步的HashMap和HashTable的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器,只需要锁住对应的segment就好了,所以可以保证高并发同步访问,提升了效率。

ConcurrentHashMap能够保证每一次调用都是原子操作,但是并不保证多次调用之间也是原子操作。

 

ThreadPoolExecutor 的内部工作原理 
有了以上定义好的数据,下面来看看内部是如何实现的 。 Doug Lea 的整个思路总结起来就是 5 句话: 
1. 如果当前池大小 poolSize 小于 corePoolSize ,则创建新线程执行任务。 
2. 如果当前池大小 poolSize 大于 corePoolSize ,且等待队列未满,则进入等待队列 
3. 如果当前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待队列已满,则创建新线程执行任务。 
4. 如果当前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待队列已满,则调用拒绝策略来处理该任务。 
5. 线程池里的每个线程执行完任务后不会立刻退出,而是会去检查下等待队列里是否还有线程任务需要执行,如果在 keepAliveTime 里等不到新的任务了,那么线程就会退出。

Executor包结构

这里写图片描述

这里写图片描述

这里写图片描述

CopyOnWriteArrayList : 写时加锁,当添加一个元素的时候,将原来的容器进行copy,复制出一个新的容器,然后在新的容器里面写,写完之后再将原容器的引用指向新的容器,而读的时候是读旧容器的数据,所以可以进行并发的读,但这是一种弱一致性的策略。 
使用场景:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。

 

posted on 2017-12-02 15:46  陈国利  阅读(228)  评论(0编辑  收藏  举报