① volatile保证可见性 ,即它会保证修改的值当即被更新到主存中,当有其余线程须要读取时,它会去内存中读取新值算法
(Java 内存模型规定了 全部的变量都存储在主内存中,每条线程还有本身的工做内存,线程的工做内存中保存了被该线程所使用的到的变量,线程对变量的操做都必须在工做内存中进行,不一样线程之间也没法直接访问对方工做内存中的变量,线程间变量值的传递须要经过主内存来完成)数组
② 禁止进行指令重排序缓存
使用volatile 必须具有如下2个条件:安全
对变量的写操做不依赖当前值bash
该变量没有包含在具备其余变量的不变式中数据结构
线程池的优点:多线程
① 下降系统资源消耗,经过重用已存在的线程,下降线程建立和销毁形成的消耗;并发
② 提升系统响应速度,当有任务到达时,无需等待新线程的建立便能当即执行;app
③ 方便线程并发数的管控,线程如果无限制的建立,不只会额外消耗大量系统资源,更是占用过多资源而阻塞资源或oom等情况,从而下降系统的稳定性,线程池有效管控线程,统一分配、调优,提供资源使用率;ide
线程池关闭方式:
① shutdown() : 将线程池设置成shutdown状态,而后中断全部没有正在执行任务的线程。
② sutdownNow(): 将线程池设置成stop状态, 而后中断全部任务(包括正在执行的任务)的线程,并返回等待执行任务的列表。
Java中有四种不一样功能的线程池:
一、newFixedThreadPool : 该线程池锁容纳的最大线程数就是所设置的核心线程数,若是线程池中的线程处于空闲状态,它们不会被回收,除非是这个线程池被关闭了,若是全部的线程都处于活动状态,新任务就会处于等待状态,直到有线程空闲出来。优点:可以更快速响应外界请求。
二、newCachedThreadPool : 该线程池中核心线程数为0,最大线程数为Integer.Max_Value,当线程池中的线程都处于活动状态的时候,线程池就会建立一个新的线程来处理任务,该线程池中的线程超时时长为60秒,,因此当线程处于闲置状态超过60秒的时候就会被回收
三、newScheduledThreadPool : 核心线程数是固定的,当非核心线程处于限制状态时会当即被回收,经常使用于执行定时任务。
四、newSingleThreadExecutor : 该线程池只有一个核心线程,对于任务队列没有大小限制,也就意味着这一任务处于活动状态时,其余任务都会在任务队列中排队等候依次执行。
synchronized void setA() throws Exception() {
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception() {
Thread.sleep(1000);
}
复制代码
1、Java 内存区域
2、Java 内存模型
Java 内存模型规定了全部的变量都存储到主内存中,每条线程还有本身的工做内存,线程的工做内存中保存了被该线程使用到的变量,线程对变量的全部操做都必须在工做内存执行,不一样线程之间也没法直接访问对方工做内存中的变量,线程间变量值的传递须要经过主内存来完成。
① 类加载机制定义:把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型。
②双亲委派模型:若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,所以,全部的类加载请求最终都应该被传递到顶层的启动类加载器(Bootstrap ClassLoader)中。只有当父加载器在它的搜索范围内没有找到所需的类时,子加载器才会尝试本身去加载该类。
这样的好处是不一样层次的类加载器具备不一样优先级,好比全部Java对象的超级父类Object,位于rt.jar,不管哪一个类加载器加载该类,最终都是由启动加载器进行加载,保证安全,若是开发者本身编写一个Object类放入程序中,虽能正常编译,但不会被加载运行,保证不会出现混乱。
标记-清除算法 ① 首先标记出全部须要回收的对象 ② 在标记完成后统一回收全部被标记的对象
缺点:标记和清除两个过程效率不高,标记清除以后产生大量不连续的内存碎片,空间碎片太多可能会致使运行过程当中须要分配较大对象时,没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做
复制算法
将可用内存按照容量大小划分为大小相等的两块,每次只使用其中的一块,当一块内存使用完了,就将还存活着的对象复制到另外一块上面,而后把使用过的内存空间一次清理掉,这样使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片等复杂状况。
缺点:将内存缩小为原来的一半。
标记-整理算法 复制收集算法在对象存活率比较高时,就要进行比较多的复制操做,效率就会变低; 标记过程仍然与 “标记-清除”算法同样,可是后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉边界之外的内存。
分代收集算法 通常把Java堆分为新生代和老生代, 这样就能够根据各个年代的特色采用最适合的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少许存活,那就选用复制算法。
在老生代中,由于对象存活率高,没有额外空间对它进行分配担保,就必须采用“标记-清除”或“标记-整理”算法来进行回收。
1、引用计数法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1;任什么时候刻计数器为0的对象就是不可能被再使用的。
主流的JVM里面没有选用引用计数法来管理内存,其中最主要的缘由就是它很难解决对象间的互循环引用的问题。
2、可达性分析算法
经过一些称为“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用连,当一个对象到GC Roots没有任何引用连相连时,证实此对象是不可用的,因此它们会被断定为可回收对象。如图B的对象是不可达的
在Java中,能够做为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈JNI(通常说的Native方法)引用的对象
复制代码
LruCache的核心思想就是要维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,放在队尾,即将被淘汰,而最近访问的对象放在对头,最后被淘汰。 如图所示:
这个队列是由LinkedHashMap来维护的,而LinkedHashMap是由数组+双向链表 的数据结构来实现的,其中双向链表的结构能够实现访问顺序和插入顺序,使得LinkedHashMap中的key,value对按照必定顺序排列起来。
经过下面构造函数来指定LinkedHashMap中双向链表的结构是访问顺序仍是插入顺序。
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
复制代码
其中accessOrder设置为true 则为访问顺序,为false,则为插入顺序。 以具体的例子解释,当设置为true 时
public static final void main(String[] args) {
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
map.put(0, 0);
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
map.put(5, 5);
map.put(6, 6);
map.get(1);
map.get(2);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
复制代码
输出结果:
0:0
3:3
4:4
5:5
6:6
1:1
2:2
复制代码
即最近访问的最后输出,那么就正好知足的LRU缓存算法的思想,可见LruCache巧妙实现,就是利用了LinkedHashMap的这种数据结构。 下面咱们在LruCache源码中具体查看,怎么应用LinkedHashMap来实现缓存的添加,得到和删除的。
/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
复制代码
从LruCache的构造函数中能够看到正是用了LinkedHashMap的访问顺序。
put()方法
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
复制代码
能够看到put()方法并无什么难点,重要的就是在添加过缓存对象后,调用 trimToSize()方法,来判断缓存是否已满,若是满了就要删除近期最少使用的算法。
trimToSize()方法
/**
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
复制代码
trimToSize()方法不断地删除LinkedHashMap中队尾的元素,即近期最少访问的,直到缓存大小小于最大值。
当调用LruCache的get()方法获取集合中的缓存对象时,就表明访问了一次该元素,将会更新队列,保持整个队列是按照访问顺序排序。这个更新过程就是在LinkedHashMap中的get()方法中完成的。
先看LruCache的get()方法
public final V get(K key) {
//key为空抛出异常
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//获取对应的缓存对象
//get()方法会实现将访问的元素更新到队列头部的功能
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
复制代码
其中LinkedHashMap的get()方法以下:
public V get(Object key) {
LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
if (e == null)
return null;
//实现排序的关键方法
e.recordAccess(this);
return e.value;
}
复制代码
调用recordAccess()方法以下:
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//判断是不是访问排序
if (lm.accessOrder) {
lm.modCount++;
//删除此元素
remove();
//将此元素移动到队列的头部
addBefore(lm.header);
}
}
复制代码
因而可知LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,若是满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法得到对应集合元素,同时会更新该元素到队头。
整理了最近一段时间的学习要点,作一个概括总结,后续还会再作更新。。。。