、java的技术体系是什么?
java程序设计语言
各硬件平台上的java虚拟机
class文件格式
java API
第三方java类库
2、JDK8特性
什么是Lambda表达式?
什么是匿名内部类?
3、java内存管理是?
什么是程序计数器?
什么是java栈?
什么是java堆?
什么是方法区?
对象的创建在内存中的反应?
什么是对象的结构?
哈希值是什么:
什么是对象的访问定位?
使用句柄(间接访问)
使用指针(直接访问)
什么是可达性分析法?
作为GCRoots的对象的引用才不会被垃圾回收;
虚拟机的内存结构?
内存分配的策略是什么?
什么是逃逸分析与栈上分配?
class文件是什么,它的文件结构由什么组成?
什么是字节码指令?
什么是加载和存储指令?
什么是运算指令?
什么是类型转换指令?
什么是对象创建与访问指令?
什么是操作数栈管理指令?
什么是控制转移指令?
什么是方法调用指令?
什么是异常处理指令?
什么是同步指令?
什么是类加载机制?
类加载机制的时机?
类加载的整个过程:
1.初始化过程:
不会被初始化的例子:
虚拟机引擎结构
运行时的栈帧结构是什么?
栈帧的具体结构是什么?
操作数栈的具体流程?
public class myApplication{
public static int add(int a, int b){
return a+b;
}
}
解释:运行前,a和b放在局部变量区,开始运行时,a先进栈,随后b进栈,a+b在栈中完成后弹栈,回到局部变量区中;
什么静态分配调用和动态分配调用?
静态分配调用-------多态------重载;
动态分配调用-------继承------重写;
最终的总结:
ArrayBlockingQueue(数组锁定队列)
是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口;底层以数组的形式保存数据(看成一盒循环数组),
常用的操作包括add,offer,put,(插入操作)
remove,poll,take,peek;(取出操作)
1、add:内部实际上获取的offer方法,当Queue已经满了时,会抛出一个异常,不会阻塞;
2、offer:当Queue已经满了时,返回false,不会阻塞;
3、put:当Queue已经满了时,会进入等待,只要不被中断,就会插入数据到队列中,会阻塞,可以响应中断;
其中remove和add相互对应,也就是说,调用remove方法时,假如对列为空,则抛出异常;而poll和offer相互对应;
take和put相互对应,peek方法比较特殊,前3个取出方法,都会将元素从Queue的头部溢出,但peek不会,它是获取头部元素,peek方法也不会阻塞,当队列为空,直接返回NULL;
源码分析(版本1.8):
1、保存数据结构
1 /** The queued items */ 2 final Object[] items;
2、全局锁
/** Main lock guarding all access */ final ReentrantLock lock;
这是一个掌管所有访问操作的锁,全局共享;
3、add和offer
1 public boolean add(E e) { 2 return super.add(e); 3 } 4 public boolean offer(E e) { 5 checkNotNull(e); 6 final ReentrantLock lock = this.lock; 7 lock.lock(); // 一直等到获取锁 8 try { 9 if (count == items.length) //假如当前容纳的元素个数已经等于数组长度,那么返回false 10 return false; 11 else { 12 enqueue(e); // 将元素插入到队列中,返回true 13 return true; 14 } 15 } finally { 16 lock.unlock(); //释放锁 17 } 18 }
分析:一直等待获取锁----》当获取到锁之后,比较当前的元素个数与数组长度,当相等时,队列已经满了,无法插入,返回false---->否则进行入队操作,返回true;
4、put
1 public void put(E e) throws InterruptedException { 2 checkNotNull(e); 3 final ReentrantLock lock = this.lock; 4 lock.lockInterruptibly(); //可中断的获取锁 5 try { 6 while (count == items.length) //当线程从等待中被唤醒时,会比较当前队列是否已经满了 7 notFull.await(); //notFull = lock.newCondition 表示队列不满这种状况,假如现场在插入的时候 8 enqueue(e); //当前队列已经满了时,则需要等到这种情况的发生。 9 } finally { //可以看出这是一种阻塞式的插入方式 10 lock.unlock(); 11 } 12 }
5、poll
1 public E poll() { 2 final ReentrantLock lock = this.lock; 3 lock.lock(); 4 try { 5 return (count == 0) ? null : dequeue(); //假如当前队列中的元素为空,返回null,否则返回出列的元素 6 } finally { 7 lock.unlock(); 8 } 9 }
6、take
1 public E take() throws InterruptedException { 2 final ReentrantLock lock = this.lock; 3 lock.lockInterruptibly(); 4 try { 5 while (count == 0) //线程在刚进入 和 被唤醒时,会查看当前队列是否为空 6 notEmpty.await(); //notEmpty=lock.newCondition表示队列不为空的这种情况。假如一个线程进行take 7 return dequeue(); //操作时,队列为空,则会一直等到到这种情况发生,返回出列元素; 8 } finally { 9 lock.unlock(); 10 } 11 }
7、peek
1 public E peek() { 2 final ReentrantLock lock = this.lock; 3 lock.lock(); 4 try { 5 return itemAt(takeIndex); // null when queue is empty 6 } // 实际上 itemAt 方法就是 return (E) items[i]; 7 // 也就是说 返回 数组中的第i个元素。
8 finally { lock.unlock(); }
8、remove
1 public E remove() { 2 E x = poll(); 3 if (x != null) 4 return x; 5 else 6 throw new NoSuchElementException(); 7 }
9、enqueue(入队)
1 private void enqueue(E x) { //因为调用enqueue的方法都已经同步过了,这里就不需要在同步了 2 // assert lock.getHoldCount() == 1; 3 // assert items[putIndex] == null; 4 final Object[] items = this.items; 5 items[putIndex] = x; //putIndex是下一个放至元素的坐标 6 if (++putIndex == items.length) //putIndex+1, 并且比较是否与数组长度相同,是的话,则从数组开头 7 putIndex = 0; //插入元素,这就是循环数组的奥秘了 8 count++; //当前元素总量+1 9 notEmpty.signal(); //给等到在数组非空的线程一个信号,唤醒他们。 10 }
10、dequeue(出队)
1 private E dequeue() { 2 // assert lock.getHoldCount() == 1; 3 // assert items[takeIndex] != null; 4 final Object[] items = this.items; 5 @SuppressWarnings("unchecked") 6 E x = (E) items[takeIndex]; 7 items[takeIndex] = null; //将要取出的元素指向null 表示这个元素已经取出去了 8 if (++takeIndex == items.length) //takeIndex +1,同样的假如已经取到了数组的末尾,那么就要重新开始取 9 takeIndex = 0; //这就是循环数组 10 count--; 11 if (itrs != null) 12 itrs.elementDequeued(); //这里实现就比较麻烦,下次单独出一个吧,可以看看源码 13 notFull.signal(); //同样 需要给 等待数组不满这种情况的线程一个信号,唤醒他们。 14 return x; 15 }
为什么ArrayBlockingQueue是线程安全的?
当一个线程对已经满了的阻塞队列进行入队操作时会阻塞,除非有另一个线程进行了出队操作,当一个线程都
一个空的阻塞队列进行出队操作时也会阻塞,除非有另外一个线程进行了入队操作;
CopyOnWriteArrayList的原理
这是一个ArrayList的变体,其原理为初始化的时候只有一个容器,很常一段时间,这个容器数据、数量等没有发生变化的时候,线程都是读取同一个容器的数据,所以这样大家读到的数据都是唯一、一致、安全的,但后来有一个数据进入容器,CopyOnWriteArrayList 的底层实现是先copy出一个容器(副本容器),再往新的容器里添加这个数据,最后把新容器的引用地址赋给旧容器地址,但在添加这个数据的期间,其他线程去读取数据,仍然是读取到旧容器的数据;
原理分析:
上述4个构造方法都创建CopyOnWriteArrayList对象,并且会创建一个Object类型的数组,然后赋值给成员array;
接下来了解它的增、删、改、读方法是怎么处理的了:
首先是加锁确保同一时间只有一个线程在添加元素,然后使用Arrays.copyOf() 方法复制出另一个新的数组,而且新的数据的长度比原来数组的长度+1,副本复制完毕,新添加的元素也赋值完毕,最后又把新的副本数组赋值给了旧的数组,最后在finally语句块中将锁释放;
(注:由于所有写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程
并发的读,则分几种情况:
1、如果写操作未完成,那么直接读取原数组的数据;
2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
3、如果写操作完成,并且引用已近指向新的数组,那么直接从新的数组中读取数据;
)
接着来看remove,删除元素,就是判断要删除的元素是否是最后一个,如果最后一个直接在赋值副本数组的时候,复制长度为旧数组的length-1, 但是如果不是最后一个元素,就先复制旧的数组的index前面元素到新的数组中,然后在复制旧数组中index后面的元素到数组中,最后再把新数组复制给旧数组的引用;
CopyOnWriteArrayList的使用场景
1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组较多的情况下,可能导致young gc或 full gc
2、不能用于实时读的场景,因为调用一个set操作之后,读取到的数据还可能是旧的;
3、适用于读多写少的场景;