zoukankan      html  css  js  c++  java
  • 面试:面经笔记 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.项目难点?


  • 相关阅读:
    有关获取session属性时报nullPointException(空指针异常)的解决方案
    常用的正则表达式集锦
    使用java实现持续移动的小球
    Java基础系列--包装类
    java中关于类的封装与继承,this、super关键字的使用
    简单了解static
    【01】npm/cnpm安装
    【转】Nodejs学习笔记(二)--- 模块
    【转】Nodejs学习笔记(一)--- 简介及安装Node.js开发环境
    【转】axios的基本使用
  • 原文地址:https://www.cnblogs.com/buwenyuwu/p/7162136.html
Copyright © 2011-2022 走看看