面试:面经笔记 2017.7

按牛客网讨论区笔经面经的发表时间排序。


阿里内推,蚂蚁金服---java开发工程师第一次电话面试

1.TCP三次握手

(记住1.过程;2.状态变化;3.几个常见问题)

第一次:客户端给服务器发送syn包x;SYN_SENT

第二次:服务器接收到syn包,返回一个syn包y 和 一个ack包x+1; SYN_RECV

第三次:客户端收到syn+ack包,向服务器发送ack包。ESTABLISHED

为什么三次握手?

防止已失效的连接请求报文段重传。

四次挥手?

把三次握手的第二次分解,先发ack包,再发fin包。

第一次:主动关闭方发送fin包x,关闭数据传送; FIN_WAIT1  CLOSE_WAIT

第二次:被动方发送ack包x+1; FIN_WAIT2

第三次:被动方发送fin包y,关闭数据传送; TIME_WAIT  LASH_ACK

第四次:主动方发送ack包y+1;

为什么四次握手?

被动方收到FIN包时,并不会立即关闭socket,所以先回复一个ack包。等到被动方所有数据发送完,再发fin包。

为什么TIME_WAIT/等待2MSL?

MSL是报文最大生存时间;主动方发出最后一个ACK包进入TIME_WAIT状态,目的是防止最后一个ACK包对方没接收到,那么对方在超时后将重发第三次握手的FIN包。 A->ACK->B,等待ACK到达对方时间MSL,等待FIN超时重传MSL,所以如果2MSL时间没有收到FIN,说明对方安全收到FIN。

2.在浏览器访问一个网址的过程?

  1.首先浏览器通过DNS解析网址的IP地址,通过IP找到服务器路径;

  2.根据IP地址向服务器发送一个HTTP请求;

  3.服务器收到请求,返回响应;

  4.浏览器对网页解析,渲染显示。

涉及各层协议?

应用层:HTTP、DNS、(DNS解析域名为目的IP,通过IP找到服务器路径,客户端向服务器发起HTTP会话)

传输层:TCP、 (HTTP会话会被分成报文段,添加源、目的端口;TCP协议进行主要工作)

网际层:IP、(ARP)、ICMP、(为数据包选择路由,IP协议进行主要工作)

链路层:PPP、(ARP)(发送IP数据包到达服务器的地址,ARP协议将IP地址转成MAC地址)

3.Linux文件的权限;

4.排序算法有哪些?时间复杂度是?

(还需要记住最好最坏时间复杂度、稳定性)

O(n^2):

选择排序:O(n^2)、O(n^2); 不稳定

冒泡排序:O(n)、O(n^2); 稳定

插入排序:O(n)、O(n^2); 稳定

O(nlogn):

快速排序:O(nlogn)、O(n^2);空间:O(logn) 不稳定

归并排序:O(nlogn)、O(nlogn);空间:O(n)  稳定

堆排序:O(nlogn)、O(nlogn);空间:O(1) 稳定

5.在线编程?


 

阿里一面17分钟

1.==和equals的区别?

(1.基本类型; 2.基本类型封装;3.String;4.非字符串变量)

equals()是Object类的方法;

(1) 如果是基本类型比较,那么只能用==来比较,用equals会编译错误,因为不是对象。int a = 3;

(2) 对于基本类型的包装类型,比如Boolean、Character、Byte、Shot、Integer、Long、Float、Double等的引用变量,==是比较地址的,而equals是比较内容的。Integer n1 = new Integer(30);

(2.5)对于String a = “a”; Integer b = 1;这种类型的特有对象创建方式,==的时候值是相同的。

(3)对于字符串变量来说,使用“==”和“equals()”方法比较字符串时,其比较方法不同。

“==”比较两个变量本身的值,即两个对象在内存中的首地址。

“equals()”比较字符串中所包含的内容是否相同。

String s1 = "123"; 
String s2 = "123"; 

String s4 = new String("123"); 
String s5 = new String("123"); 

s1==s2 true;   s1.equals(s2) true;   

s4==s5 false;   s4.equals(s5) true;

s1==s4 false;   s1.equals(s4) true;

s1/s2分别指向字符串常量"123"创建的对象,在常量池里只有一个对象,内容为"123";

s4/s5两个引用对象指向的对象内容相同,但是new操作符创建的,内存中分配两块空间给这两个对象,所以内存地址不同。

(4)对于非字符串变量来说,"=="和"equals"方法的作用是相同的都是用来比较其对象在堆内存的首地址,即用来比较两个引用变量是否指向同一个对象。

2.string是不是基本数据类型,

不是,String是类类型,基本类型有八种:

整型4种:byte/short/int/long  字节数:1/2/4/8

字符型1种:char  2

浮点型2种:float/double  4/8

布尔型1种:boolean 1/8

一个字节等于8位,等于256个数,就是-128到127

大写的B表示Bytes=字节;小写的b表示bit=位;1byte=8bit;

自动转换:(小可转大,大转小会失去精度)

byte -> short/char -> int -> long -> float -> double

3.char能不能存放汉字?

能,一个char字符可以存储一个中文汉字。

4.error/exception/runtime exception区别?

Error和Exception都实现了Throwable接口 
Error指的是JVM层面的错误,比如内存不足OutOfMemoryError
Exception 指的是代码逻辑的异常,比如下标越界OutOfIndexException

Exception分为可查异常CheckedException和运行时异常RuntimeException:

可查异常是必须处理的异常,要么try catch住,要么往外抛,谁调用,谁处理,比如 FileNotFoundException、IOException、SQLException等。如果不处理,编译器就不让你通过。

运行时异常 又叫做非可查异常,在编译过程中,不要求必须进行显示捕捉。

常见的Runtime Excepiton?

NullPointerException 空指针异常
ArithmeticException 算术异常,比如除数为零
ClassCastException 类型转换异常
ConcurrentModificationException 同步修改异常,遍历一个集合的时候,删除集合的元素,就会抛出该异常 
IndexOutOfBoundsException 数组下标越界异常
NegativeArraySizeException 为数组分配的空间是负数异常

为什么分两种异常?

Java之所以会设计运行时异常的原因之一,是因为下标越界,空指针这些运行时异常太过于普遍,如果都需要进行捕捉,代码的可读性就会变得很糟糕。

5.object类的方法?

9种;(简要介绍各方法)

  1.对象的复制/获取/String/释放:clone/getClass/toString/finalize;

  2.hash:equals/hashCode;

  3.多线程:wait/notify/notifyAll

clone:实现对象的浅复制;

getClass:获得运行时类对象;

finalize:用于释放资源;

equals:比较对象是否相等;基本类型不可以用equals,对于String类型“equals”和“==”作用不同;

hashcode:用于hash寻找;

wait:使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁;wait()方法一直等待,直到获得锁或者被中断。wait(longtimeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生:

(1)其他线程调用了该对象的notify方法。

(2)其他线程调用了该对象的notifyAll方法。

(3)其他线程调用了interrupt中断该线程。

(4)时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

notify:唤醒在该对象上等待的某个线程

6.jvm垃圾回收

7.linux查看日志文件的方式?


蚂蚁金服Java面经

1.Java都学了些什么?
答:集合、IO、多线程、框架等等

2.说说多线程
答:说了一下多线程的实现,同步,优化

(tips:对于大范围的内容要整理出目录,不然会很乱。)

进程和线程的区别 感觉有点偏题就不写了

多线程的实现?

三种方法:1.继承Thread类;2.实现Runnable接口;3.使用Executor创建线程池;

多线程的同步?

(1)同步方法:synchronized修饰的方法;

(2)同步代码块:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

  同步方法和同步代码块的区别是什么?

  答:同步方法默认用this或者当前类class对象作为锁; 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法。

(3)使用volatile实现同步:每次线程要访问volatile修饰的变量时都是从内存中读取,而不是从缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

(4)使用重入锁实现线程同步:ReentrantLock是concurrent包的类;常用方法有lock()和unlock();可以创建公平锁;支持非阻塞的tryLock(可超时);需要手动释放锁。

(5)使用ThreadLocal实现线程同步:每个线程都创建一个变量副本,修改副本不会影响其他线程的副本。ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量)。

多线程的优化?

影响多线程性能的问题:死锁、过多串行化、过多锁竞争等;

预防和处理死锁的方法:

  1)尽量不要在释放锁之前竞争其他锁;一般可以通过细化同步方法来实现;

  2)顺序索取锁资源;

  3)尝试定时锁tryLock();

降低锁竞争方法:

  1)缩小锁的范围,减小锁的粒度;

  2)使用读写分离锁ReadWriteLock来替换独占锁:来实现读-读并发,读-写串行,写-写串行的特性。这种方式更进一步提高了可并发性,因为有些场景大部分是读操作,因此没必要串行工作。


3.说一下线程池,线程池里面的线程的状态有哪些?

线程池:

(1.创建;2.参数;)

线程池的顶级接口是Executor,是执行线程的工具;真正的线程池接口是ExecutorService。ThreadPoolExecutor是ExecutorService的默认实现。

ThreadPoolExecutor的参数有:

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize-池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。

threadFactory - 执行程序创建新线程时使用的工厂。

handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

线程的状态:(把那张图将熟悉)

包括New、Runnable、Running、Blocked、Dead状态;

1)New:Thread t = new Thread();

2)Runnable:t.start()后进入Runnable状态;位于可运行线程池中,等待被线程调度选中,获得CPU使用权;

3)Running:Runnable状态的线程获得了cpu时间片;

4)Blocked:三种情况;

  1.等待阻塞:o.wait(),释放锁;进入等待队列;o.notify()/notifyAll() 进入锁池;

  2.同步阻塞:同步锁被别的线程占用;进入锁池;

  3.其他阻塞:1)Thread.sleep();2)t2.join();3)等待用户输入(发出I/O请求);不会释放锁;当sleep时间结束,t2线程结束,I/O处理完成后进入Runnable状态;

5)Dead:run()/main()执行结束 或者 异常退出;线程结束生命周期。

4.数据结构学了些什么?

数组、HashMap、栈、队列、链表、树;(HashMap也算?)

(把方向引向自己擅长的部分)

5.Hashmap和Hashtable的区别?

相同点:都实现了Map接口;

不同点-两个方面:null值,同步;

HashMap允许键和值是null,HashTable不允许;

HashTable是同步的,HashMap不是;

6.Hashmap的数据结构,Hash的具体实现(这块答得不好)

(讲HashMap:1.结构+原理;2.其他参数-容量、负荷系数、阈值;)

  1)HashMap是有数组+链表组成,Entry数组是HashMap的主体,链表是为了解决Hash冲突;

  2)HashMap的Entry数组的元素可以看作是一个个散列桶,每个桶是一个单链表;每个Entry内部类有四个字段:key/value/hash/next;

  3)执行put时,根据key的hashcode定位到桶;遍历单链表,利用key.equals()检查key是否存在;如果存在则覆盖;否则新建Entry放在头部;

  4)执行get时,根据key的hashcode定位到桶;遍历单链表,利用key.equals()获取对应的Entry,返回它的value;

  5)参数:容量capacity(默认16)、负载系数loadFactor(默认0.75)、阈值threshold=容量*负载系数。数组容量capacity必须是2的n次方,当键值对个数>threshold(12)时,扩容:将数组扩容为原来容量的二倍。

几点补充:

  1)根据hashcode定位桶步骤:

int hash = hash(key.hashCode());     //计算key.hashcode()的hash值,hash函数由hashmap自己实现
int i = indexFor(hash, table.length);//获取将要存放的数组下标

 也就是首先计算key的hashcode(),再对该值map的自定义hash()(将hash值打散,使插入的Entry落在不同的桶上,提高查询效率),再根据得到的hash值调用indexFor()方法;indexFor(h,length)方法:将hash值与entry数组的长度-1按位与;

    /**
     * "按位与"来获取数组下标
     */
    static int indexFor(int h, int length) {
        return h & (length - 1);
    }

  2)为什么保持Entry数组大小2的n次方?

当length总是2的n次方时,h& (length-1)运算等价于对length取余,也就是h%length,但是&比%具有更高的效率。

参考:HashMap源码

7.设计模式有了解吗?
答:谈了一下单例模式、工厂模式、代理模式,顺便说了一下Spring的AOP是基于代理模式的,可以实现日志记录等功能。

代理模式

8.数据库事务你了解吗?脏读是什么,幻读是什么?

(说一下事务的四个特性+四个冲突+四个隔离级别) 


阿里内推一面,已跪

[阿里] [c++]

1.讲一下Linux下如何将源文件逐步编译成目标文件的过程 

2.你简历上写熟悉TCP/IP协议,那你说一下TCP的报头吧。

/*TCP头定义,共20个字节*/
typedef struct _TCP_HEADER 
{
 short m_sSourPort;              // 源端口号16bit
 short m_sDestPort;              // 目的端口号16bit


 unsigned int m_uiSequNum;         // 序列号32bit
 unsigned int m_uiAcknowledgeNum;  // 确认号32bit


 short m_sHeaderLenAndFlag;        // 前4位:TCP头长度;中6位:保留;后6位:标志位
 short m_sWindowSize;            // 窗口大小16bit


 short m_sCheckSum;              // 检验和16bit
 short m_surgentPointer;           // 紧急数据偏移量16bit
}__attribute__((packed))TCP_HEADER, *PTCP_HEADER;

参考:IP头、TCP头、UDP头

3. 你简历上写的掌握常用的数据结构和排序算法,那你说一个你熟悉的排序算法吧,冒泡就不用说了 。(注:原理+复杂度+手写代码)

排序主要用这六种:

O(n^2)的有:冒泡排序、插入排序、选择排序;

O(nlogn)的有:快排、归并排序、堆排序;

冒泡排序:原理是无序区两两比较,每趟得到一个最大的放在无序区最后;

  算法:外循环是趟数[0,n-1), 内循环是无序区个数[0, n-i-1);循环体是比较[j]和[j+1];

插入排序:是把数组分成有序区和无序区两部分,每次从无序区取出一位作为tar值,从后向前遍历有序区,大于tar则后移,最后放到目标位置;

  算法:外循环是无序区长度[1,n);定义a[i]为tar值;内循环j指向i,比较a[j-1]与tar,大于则往后顺移a[j-1];最后a[j]赋值;

选择排序:也是分为有序区和无序区,每次从无序区选择一位最小的放到有序区末尾;

  算法:外循环是趟数[0,n-1), 内循环是无序区个数[i+1,n);循环体是比较[i]和[j];

快排:每次排序确定一个数的位置,比该数小的移到左边,大的移到右边。一趟快排的算法是:

  • 设置两个首尾指针lo、hi;
  • 以第一个元素作为key值;
  • 从hi向前搜索,如果大于key则hi--,小于key则[lo]=[hi]把[hi]移到前面;
  • 从lo向后搜搜,如果小于key则lo++,大于key则[hi]=[lo]把[lo]移到后面;
  • 重复3、4步骤,直到lo=hi;返回lo位置。

上述过程就是partition函数;以partition函数返回的位置,对左右两边递归。

  算法:(如上)partition(a,lo,hi)函数:定义key,while循环(lo<hi),内部(lo<hi && a[hi]>=key);最后a[lo]赋值,返回lo; sort(a,lo,hi)函数:判断(lo<hi),index,递归;

归并排序:将数组分成若干个小数组,将已有序的数组两两归并得到完全有序数组。每趟归并的算法是:

  • 申请空间tmp[],大小是两个已排数组的和,[hi-lo+1];
  • 设定两个指针,分别指向两个已排数组的起始位置;
  • 比较两个指针元素的值,选择小的元素放到tmp[]里,并移动指针;
  • 重复步骤3,直到某一指针到数组尾;
  • 将另一序列剩下的所有元素复制到tmp[]里;
  • 将tmp[]数组覆盖原num[]数组;

  算法:merge(a,lo,mid,hi)方法:tmp[]数组,左右指针+临时指针,比较两数组小的保存,保存剩余数组,赋值数组;mergeSort(a,lo,hi) mid, if()判断:左右递归,merge();注意左右递归一定是左边(lo,mid),右边(mid+1, hi);因为mid偏左,不然会死循环。

堆排序:堆排序包括两个过程,首先是根据元素建堆,时间复杂度O(n),然后将堆的根节点取出(与最后一个节点交换),将前面N-1个节点进行堆调整。直至所有节点都取出。堆调整时间复杂是O(lgn),调用了n-1次,所以堆排序时间复杂度是O(nlgn);

  • 建堆:建堆是不断调整堆的过程;从len/2出开始调整(最后一个父节点)。最后一层父节点最多下调1次,倒数第二层最多下调2次,顶点最多下调H=logN次。而最后一层父节点共有2^(H-1)个,倒数第二层公有2^(H-2),顶点只有1(2^0)个,所以总共的时间复杂度为s = 1 * 2^(H-1) + 2 * 2^(H-2) + ... + (H-1) * 2^1 + H * 2^0。将H代入后s= 2N - 2 - log2(N),近似的时间复杂度就是O(N)。
  • 调整堆:思想是比较节点i和它的孩子节点left(i),right(i),若i不是最大则调换。

  

蚂蚁金服java工程师面经 

 
1 自我介绍
2 介绍项目,在项目中图片存储在公有云上加密问题,项目中为什么用redis以及怎么实现相关功能的
 
 
3 说说你学java都学了哪些内容
基础的:面向对象、操作符、控制流程(条件判断、循环语句)、数组、类和对象、接口与继承等;
中级的:异常处理、集合框架、多线程等;
J2EE的:Servlet、JSP等;
框架:Spring、Spring MVC、MyBatis、Spring Boot、Spring Data JPA;
 
 
 
4 项目中用过的开源框架,为什么要用springboot 和 hibernate
 
 
5 spring的核心功能是什么,介绍一下AOP以及怎么实现的,jdk代理和cglib代理的区别
 
核心功能是IoC反转控制和AOP面向切面编程;
AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
JDK动态代理和CGLIB字节码生成的区别?
 * JDK动态代理只能对实现了接口的类生成代理,而不能针对类
 * CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
参考:AOP原理(好,读!)、JDK与CGLib
 
 
6 数据库的四大特性,事务的隔离级别,幻读。
 
四大特性是:ACID 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)+介绍四个特性概念;
事务隔离级别:读未提交(Read uncommitted)、读已提交(Read committed)、可重复读(Repeatable read)和串行化(Serializable)+介绍;
四大冲突问题:脏读、不可重复读、幻读、更新丢失;
 
 
7 计算机网络都学了什么(类似说书上的目录),OSI七层每层的任务,数据链路层的功能(答得不全)和协议。
 
包括:OSI参考模型以及各层的学习、TCP/IP协议、HTTP这些;
OSI七层自底向上是:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层;
各层功能和协议:
物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)
数据链路层:将比特组装成帧和点到点的传递(帧Frame) PPP点对点协议、ARP地址解析协议;
网络层:负责数据包从源到宿的传递和网际互连(包Packet) IP、ICMP、RARP、RIP
传输层:提供端到端的可靠报文传递和错误恢复(段Segment) TCP、UDP
会话层:建立、管理和终止会话(会话协议数据单元SPDU)
表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)
应用层:允许访问OSI环境的手段(应用协议数据单元APDU)FTP、DNS、HTTP
(注:ARP在OSI里是链路层,在TCP/IP里是网络层)
 
 
8 数据结构都学了什么,排序算法最快的是哪种说说原理,哪些是不用申请额外空间的
数组、链表、队列、栈、树;
 
9 树的非递归遍历以及三种遍历知道哪两种不能确定一棵树
中序前序后序,非递归使用Stack栈来实现;
中序:向左一直遍历并且放入stack里,直到最左边,pop、add和遍历右边;
前序:先放入root,pop,然后依次放入右边和左边的;
后序:前序的次序变换一下,并且用llist.add(0,val);
 
10 介绍一下二叉平衡树
二叉平衡树就是两个子树的深度差不超过1;
判断二叉平衡树的算法实现:
自顶向下:depth求深度,要求两个字数深入差不超过1并且两个字数均为平衡树;
自底向上:不平衡的子树用-1标注;
 
11 是否看过jdk源码,说说你说看过的
集合框架的HashMap、Concurrent下的ConHashMap、Executor等;
(todo)
 
12 说说concurrent包下的类,然后问了一下Reentrantlock.
Concurrent包下的类包括:
并发集合类:ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、ArrayBlockingQueue、LinkedBlockingQueue;
原子类:AtomicInteger
线程池:ThreadPoolExecutor、Executor;
锁:ReentrantLock、ReentrantReadWriteLock;
(重点准备!)
 
13 以后的职业规划
 

 

人生第一次技术面试 蚂蚁金服 

 

1.ArrayList和LinkedList的区别

三个方面:1.实现;2.查询、增删;3.内存;

 

2.知道乐观锁,悲观锁么?什么情况下用乐观什么情况下用悲观么?

乐观锁:默认读数据的时候不会修改,所以不会上锁;

悲观锁:默认读数据的时候会修改,所以会上锁;

乐观锁适用于多读写比较少的情况,省去锁的开销,加大系统的吞吐量。

 

3.volatile关键字的作用?i++是原子性的么?

  在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 

  要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。 

  Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 

  使用volatile关键字修饰变量,线程要访问变量时都是从内存中读取,而不是从缓存当中读取,因此每个线程访问到的变量值都是一样的。

  i++不是原子操作,分为三个阶段:内存到寄存器、寄存器自增、写会内存;这三个阶段中间都可以被中断分离开.

 

4.Java内存模型?为什么设置工作内存和主内存?

PS:JVM内存模型和JMM(Java内存模型)没有关系。JMM的目的是为了解决Java多线程对共享数据的读写一致性问题。

 
所有线程共享主内存
每个线程有自己的工作内存
refreshing local memory to/from main memory must  comply to JMM rules
 
每个线程都有自己的执行空间(即工作内存),线程执行的时候用到某变量,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作:读取,修改,赋值等,这些均在工作内存完成,操作完成后再将变量写回主内存;
 
工作内存可类比高速缓存,为了获得更好的执行性能。
 
 
5.GC的过程。
对象根据被存活的时间被分为:年轻代、年老代、永久代;
年轻代:对象被创建时,内存分配首先发生在年轻代,年轻代分为三个区域:Eden区、S0、S1;
创建时分配在Eden去,满的时候执行Minor GC,消亡的对象清理,剩余的复制到S0;Eden再满时,Minor GC,存活的复制到S1,将S0消亡的清除,可以晋级的到Old,存的的到S1。切换多次,仍然存活的复制到老年代。
年老代:内存不足时执行Full GC,标记整理。
 
6.GC时会对程序有什么影响?当发现虚拟机频繁GC时应该怎么办?
"stop-the-world" 机制简称STW,即,在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集器线程之外的线程都被挂起。
频繁GC:需要调优,比如控制新生代老年代比例,控制进入老年代前生存次数,控制年轻代eden和survivor比例(默认8:1)等;
 
7.Java8有什么新特性,知道lambda表达式么?

1.Lambda表达式的形式化表示如下所示

Parameters -> an expression 

2.如果Lambda表达式中要执行多个语句块,需要将多个语句块以{}进行包装,如果有返回值,需要显示指定return语句,如下所示:

Parameters -> {expressions;};

3.如果Lambda表达式不需要参数,可以使用一个空括号表示,如下示例所示

() -> {for (int i = 0; i < 1000; i++) doSomething();};

5.如果Lambda表达式只有一个参数,并且参数的类型是可以由编译器推断出来的,则可以如下所示使用Lambda表达式,即可以省略参数的类型及括号

Stream.of(datas).forEach(param -> {System.out.println(param.length());});

参考:lambda表达式 

9.设计模式知道么?

单例模式、工厂模式、观察者模式;

单例模式:

1.特点:

  • 只能有一个实例;
  • 必须自己创建自己的唯一实例;
  • 必须给其他对象提供这一实例;

2.单例模式有两种写法:饿汉式和懒汉式;饿汉式是一旦类加载了,就把单例初始化完成;而懒汉式只有在调用getInstance的时候,才初始化这个单例。

3.懒汉式单例:

a.写代码(构造方法,创建实例,get);

b.非线程安全;

c.三种线程安全的代码+优缺点

  syn:每次获取都需要同步,影响性能;

  双重检查:判空后锁住类;为什么要第二次检查?创建实例的操作非原子化;在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗;

  静态内部类:LazyHolder;利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗;

4.饿汉式单例:线程安全;在类创建的同事就实例化一个静态对象出来。

工厂模式:

见印象笔记/设计模式;工厂方法模式和抽象工厂模式区别;

观察者模式:

角色:抽象观察者-update、具体观察者、抽象被观察者-attach/detach/notify、具体被观察者;

使用场景:一个对象状态更新,其他对象同步更新,只需要将自己更新通知给其他对象而不需要知道其他对象细节。解耦,各自变换互不影响。

10.项目难点?


原文地址:https://www.cnblogs.com/buwenyuwu/p/7162136.html