Java面试题汇总

一、基础篇

1. 基本功

1.1 面向对象的特征

面向对象的编程语言有封装、继承 、抽象、多态等4个主要的特征。

1. 封装: 封装是保证软件部件具有优良的模块性的基础,封装的目标就是要实现软件部件的“高内聚、低耦合”,防止程序相互依赖性而带来的变动影响。在面向对象的编程语言中,对象是封装的最基本单位,面向对象的封装比传统语言的封装更为清晰、更为有力。面向对象的封装就是把描述一个对象的属性和行为的代码封装在一个“模块”中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法可以直接访问同一个对象中的属性。通常情况下,只要记住让变量和访问这个变量的方法放在一起,将一个类中的成员变量全部定义成私有的,只有这个类自己的方法才可以访问到这些成员变量,这就基本上实现对象的封装,就很容易找出要分配到这个类上的方法了,就基本上算是会面向对象的编程了。把握一个原则:把对同一事物进行操作的方法和相关的方法放在同一个类中,把方法和它操作的数据放在同一个类中。 

2.  抽象: 抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处,并且会忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面。例如,看到一只蚂蚁和大象,你能够想象出它们的相同之处,那就是抽象。抽象包括行为抽象和状态抽象两个方面。例如,定义一个Person类,

如下: class  Person { String name; int age; } 人本来是很复杂的事物,有很多方面,但因为当前系统只需要了解人的姓名和年龄,所以上面定义的类中只包含姓名和年龄这两个属性,这就是一种抽像,使用抽象可以避免考虑一些与目标无关的细节。我对抽象的理解就是不要用显微镜去看一个事物的所有方面,这样涉及的内容就太多了,而是要善于划分问题的边界,当前系统需要什么,就只考虑什么。

3.  继承: 在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。继承是子类自动共享父类数据和方法的机制,这是类之间的一种关系,提高了软件的可重用性和可扩展性。

4. 多态: 多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。多态性增强了软件的灵活性和扩展性。例如,下面代码中的UserDao是一个接口,它定义引用变量userDao指向的实例对象由daofactory.getDao()在执行的时候返回,有时候指向的是UserJdbcDao这个实现,有时候指向的是UserHibernateDao这个实现,这样,不用修改源代码,就可以改变userDao指向的具体类实现,从而导致userDao.insertUser()方法调用的具体代码也随之改变,即有时候调用的是UserJdbcDao的insertUser方法,有时候调用的是UserHibernateDao的insertUser方法: UserDao userDao = daofactory.getDao(); userDao.insertUser(user);

 

1.2 finalfinallyfinalize的区别

(1)final:在java中,final可以用来修饰类,方法和变量(成员变量或局部变量)。

当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰,但要注意:final类中所有的成员方法都会隐式的定义为final方法。

修饰方法

使用final方法的原因主要有两个:

(1) 把方法锁定,以防止继承类对其进行更改。

(2) 效率,在早期的java版本中,会将final方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要final方法进行这些优化了。

final方法意味着“最后的、最终的”含义,即此方法不能被重写。

注意:若父类中final方法的访问权限为private,将导致子类中不能直接继承该方法,因此,此时可以在子类中定义相同方法名的函数,此时不会与重写final的矛盾,而是在子类中重新地定义了新方法。

修饰变量

final成员变量表示常量,只能被赋值一次,赋值后其值不再改变。类似于C++中的const。

当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。 

final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。

 

在java中,String被设计成final类,那为什么平时使用时,String的值可以被改变呢?

字符串常量池是java堆内存中一个特殊的存储区域,当我们建立一个String对象时,假设常量池不存在该字符串,则创建一个,若存在则直接引用已经存在的字符串。当我们对String对象值改变的时候,例如 String a="A"; a="B" 。a是String对象的一个引用(我们这里所说的String对象其实是指字符串常量),当a=“B”执行时,并不是原本String对象("A")发生改变,而是创建一个新的对象("B"),令a引用它。

(2) finally

finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。(×)(这句话其实存在一定的问题)

很多人都认为finally语句块一定会执行,但真的是这样么?答案是否定的,例如下面这个例子:

 

 

当我们去掉注释的三行语句,执行结果为:

 

为什么在以上两种情况下都没有执行finally语句呢,说明什么问题?

  只有与finally对应的try语句块得到执行的情况下,finally语句块才会执行。以上两种情况在执行try语句块之前已经返回或抛出异常,所以try对应的finally语句并没有执行。

  但是,在某些情况下,即使try语句执行了,finally语句也不一定执行。例如以下情况:

finally 语句块还是没有执行,为什么呢?因为我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。那有人说了,在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的。OK !没有问题,我们不调用 System.exit(0) 这个方法,那么 finally 语句块就一定会执行吗?

  再一次让大家失望了,答案还是否定的。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。可能有人认为死机、断电这些理由有些强词夺理,没有关系,我们只是为了说明这个问题。

易错点

在try-catch-finally语句中执行return语句。我们看如下代码:

答案:4,4,4  。    为什么呢?

  首先finally语句在改代码中一定会执行,从运行结果来看,每次return的结果都是4(即finally语句),仿佛其他return语句被屏蔽掉了。

  事实也确实如此,因为finally用法特殊,所以会撤销之前的return语句,继续执行最后的finally块中的代码。 

(3) finalize

finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。 
特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。 
  使用finalize还需要注意一个事,调用super.finalize();

  一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。

 

1.3 int和Integer有什么区别

1、Integer是int的包装类,int则是java的一种基本数据类型;
2、Integer变量必须实例化后才能使用,而int变量不需要;
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值;
4、Integer的默认值是null,int的默认值是0。

延伸: 
关于Integer和int的比较 
1、由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。

Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false

2、Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)

Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); //true

3、非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)

Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false

4、对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true

Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false

对于第4条的原因: 
java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的valueOf的定义如下:

public static Integer valueOf(int i){
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high){
        return IntegerCache.cache[i + (-IntegerCache.low)];
    }
    return new Integer(i);
}

java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了

 

1.4重载和重写的区别

1)重载发生在本类,方法名相同,参数列表不同,与返回值无关,只和方法名,参数列表,参数的类型有关(方法名相同的情况)

重载(Overload):首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。

(1):方法名必须相同

(2):方法的参数列表一定不一样。

(3):访问修饰符和返回值类型可以相同也可以不同。

其实简单而言:重载就是对于不同的情况写不同的方法。 比如,同一个类中,写不同的构造函数用于初始化不同的参数。

2)重写发生在父类子类之间,比如所有类都是继承与Object类的,Object类中本身就有equals,hashcode,toString方法等.在任意子类中定义了重名和同样的参数列表就构成方法重写.

重写(override):一般都是表示子类和父类之间的关系,其主要的特征是:方法名相同,参数相同,但是具体的实现不同。

重写的特征:

(1):方法名必须相同,返回值类型必须相同

(2):参数列表必须相同

(3):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。

(4):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。

(5):构造方法不能被重写,

简单而言:就是具体的实现类对于父类的该方法实现不满意,需要自己在写一个满足于自己要求的方法。

 

1.5抽象类和接口有什么区别

1.6说说反射的用途及实现

1.7 说说自定义注解的场景及实现

1.8HTTP 请求的 GET 与 POST 方式的区别

1.9session 与 cookie 区别

1.10session 分布式处理

1.11JDBC 流程

1.12MVC 设计思想

1.13equals 与 == 的区别

 

 

注意:
所有整型包装类对象之间 值的比较, 全部使用 equals 方法比较。
说明: 对于 Integer var = ? 在 -128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产
生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数
据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

 

1.14 匿名内部类

(自加的)

 

(1)内部类:定义在A类中的B类就是内部类,B类在A类中的地位和其他成员相同,意思就是说可以相互调用。

(2)局部内部类:定义在一个类中方法中的类。

作用范围:从定义它开始到所在方法的结束
作用:既保证了代码的私有化,又对代码进行了整理,增加了代码的可读性,操作性

class 外部类名{

    外部方法(){

        内部类名{

            内部方法(){

                

            }

        }

    }

}

(3)匿名内部类

匿名内部类(对象):定义在一个类方法中的匿名子类 对象,属于局部内部类
匿名内部类的作用:用于类的私有化,只让外部类看

匿名类,就是没有名称的类,其名称由Java编译器给出,一般是形如:外部类名称+$+匿名类顺序,没有名称也就是其他地方就不能引用,不能实例化,只用一次,当然也就不能有构造器。

匿名类就是利用父类的构造函数和自身类体构造成一个类。

上面格式中的“父类”是子类需要继承或者实现外部的类或者接口

匿名类可以继承父类的方法,也可以重写父类的方法。

匿名类可以访问外部类的成员变量和方法,匿名类的类体不可以声明称static成员变量和static方法。

匿名类由于是一个new的结果,所以其实可以赋值给一个父类对象。因此可以分为两种匿名类,成员匿名类和局部匿名类(作为函数参数)

 

2. 集合

2.1 List 和 Set 区别

2.2 List 和 Map 区别

2.3 Arraylist 与 LinkedList 区别

2.4 ArrayList 与 Vector 区别

2.5 HashMap 和 Hashtable 的区别

2.6 HashSet 和 HashMap 区别

2.7 HashMap 和 ConcurrentHashMap 的区别

2.8 HashMap 的工作原理及代码实现

2.9 ConcurrentHashMap 的工作原理及代码实现

 

3. 线程

3.1 线程基础

 

3.2创建线程的方式及实现

在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。

 

 用Runnable还是Thread?

这个问题是上题的后续,大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。

 

3.3 sleep()、join()、yield()有什么区别

(1) sleep()

sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。

(2)wait()方法

在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。

当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。

唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。

waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

(3) yield方法

暂停当前正在执行的线程对象。

yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。yield()只能使同优先级或更高优先级的线程有执行的机会。

 

3.4 说说CountDownLatch原理

CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

 

3.5 说说CyclicBarrier原理

 

3.6 说说Semaphore原理

http://www.importnew.com/21889.html

 

 Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。

 

3.7 说说 Exchanger原理

一般用于两个工作线程之间交换数据

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的。

工作原理:以一个停车场是运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。这个停车系统中,每辆车就好比一个线程,看门人就好比一个信号量,看门人限制了可以活动的线程。假如里面依然是三个车位,但是看门人改变了规则,要求每次只能停两辆车,那么一开始进入两辆车,后面得等到有车离开才能有车进入,但是得保证最多停两辆车。对于Semaphore类而言,就如同一个看门人,限制了可活动的线程数。

3.8 说说 CountDownLatch 与 CyclicBarrier 区别

 

3.9 ThreadLocal 原理分析

资料:https://www.cnblogs.com/dolphin0520/p/3920407.html

ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

如对数据库的连接操作类,此类没有添加同步锁,则多个线程间的开启与关闭就会导致一个线程打开了数据库的连接,但是另外一个线程此时关闭了数据库的连接,这就会导致多线程问题,这个时候如果把数据库的操作放在dao层,即某个类的内部:由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。

 那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

  但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

session的管理代码:

private static final ThreadLocal threadSession = new ThreadLocal();

 

public static Session getSession() throws InfrastructureException {

    Session s = (Session) threadSession.get();

    try {

        if (s == null) {

            s = getSessionFactory().openSession();

            threadSession.set(s);

        }

    } catch (HibernateException ex) {

        throw new InfrastructureException(ex);

    }

    return s;

}

3.10 讲讲线程池的实现原理

其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。

 

3.11 线程池的几种方式与使用场景

       在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类,学习Java中的线程池,就可以直接学习他了对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置

线程池比较单线程的优势在于:

a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。

 

在 Executors 类里面提供了一些静态工厂,生成一些常用的线程池建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)

但是实际开发中,尽量避免使用Executors 取创建线程池,而是通过ThreadPoolExecutor的方式, 这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。(阿里手册)注意的是查看Excutors 源码,Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的

 

Executors 返回的线程池对象的弊端如下:

FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。

CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。

1newFixedThreadPool:

创建固定大小的线程池。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

2newCachedThreadPool:

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。

3newSingleThreadExecutor:

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

4newScheduledThreadPool:

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

5newSingleThreadScheduledExecutor:

创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

使用场景:多线程的定时任务相关等。

 

3.12 线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换

1. 新建状态,当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值

2. 就绪状态,当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行

3. 运行状态,如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

4. 阻塞状态,当处于运行状态的线程失去所占用资源之后,便进入阻塞状态

5. 在线程的生命周期当中,线程的各种状态的转换过程。

 

注意:启动线程使用start()方法,而不是run()方法。永远不要调用线程对象的run()方法。调用start0方法来启动线程,系统会把该run()方法当成线程执行体来处理;但如果直按调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其他线程无法并发执行。也就是说,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。需要指出的是,调用了线程的run()方法之后,该线程已经不再处于新建状态,不要再次调用线程对象的start()方法。只能对处于新建状态的线程调用start()方法,否则将引发IllegaIThreadStateExccption异常。

3.13 锁机制

般在java中所说的锁就是指的内置锁,每个java对象都可以作为一个实现同步的锁,虽然说在java中一切皆对象, 但是锁必须是引用类型的,基本数据类型则不可以 。每一个引用类型的对象都可以隐式的扮演一个用于同步的锁的角色,执行线程进入synchronized块之前会自动获得锁,无论是通过正常语句退出还是执行过程中抛出了异常,线程都会在放弃对synchronized块的控制时自动释放锁。 获得锁的唯一途径就是进入这个内部锁保护的同步块或方法 。

Synchronized是基于JVM来保证数据同步的,而Lock则是在硬件层面,依赖特殊的CPU指令实现数据同步的

与synchronized不同的是,Lock书纯Java实现的,与底层的JVM无关。

 

3.14 说说线程安全问题

http://www.jasongj.com/java/thread_safe/

多线程编程中的三个核心问题:原子性、可见性、顺序性。

(1)原子性:这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

关于原子性,一个非常经典的例子就是银行转账问题:比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C的账户,此时B的转账请求过来了,B发现C的余额为20万,然后将其加10万并写回。然后A的转账操作继续——将30万写回C的余额。这种情况下C的最终余额为30万,而非预期的40万。

(2)可见性:可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。可见性问题是好多人忽略或者理解错误的一点。

CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。

这一点是操作系统或者说是硬件层面的机制,所以很多应用开发人员经常会忽略。

(3)顺序性:顺序性指的是,程序执行的顺序按照代码的先后顺序执行。

以下面这段代码为例

boolean started = false; // 语句1

long counter = 0L; // 语句2

counter = 1; // 语句3

started = true; // 语句4

从代码顺序上看,上面四条语句应该依次执行,但实际上JVM真正在执行这段代码时,并不保证它们一定完全按照此顺序执行。

处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。

讲到这里,有人要着急了——什么,CPU不按照我的代码顺序执行代码,那怎么保证得到我们想要的效果呢?实际上,大家大可放心,CPU虽然并不保证完全按照代码顺序执行,但它会保证程序最终的执行结果和代码顺序执行时的结果一致。

 

3.15 volatile 实现原理

3.16 synchronize 实现原理

3.17 synchronized 与 lock 的区别

3.18 CAS 乐观锁

(1)CAS全称compare and swap,一个CPU原子指令,在硬件层面实现的机制,体现了乐观锁的思想。

(2)JVM用C语言封装了汇编调用。Java的基础库中有很多类就是基于JNI调用C接口实现了多线程同步更新的功能。

 

基础类型变量自增(i++)是一种常被新手误以为是原子操作而实际不是的操作。Java中提供了对应的原子操作类来实现该操作,并保证原子性,其本质是利用了CPU级别的CAS指令。由于是CPU级别的指令,其开销比需要操作系统参与的锁的开销小。AtomicInteger使用方法如下。

AtomicInteger atomicInteger = new AtomicInteger();

for(int b = 0; b < numThreads; b++) {

  new Thread(() -> {

    for(int a = 0; a < iteration; a++) {

      atomicInteger.incrementAndGet();

    }

  }).start();

}

(1)synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。

(2)CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

如对int基本类型的操作,可以换成AtomicInteger来实现。

3.19 ABA问题

3.20 乐观锁的业务场景及实现方式

 

二、核心篇

1. 数据存储

1.1 MySQL 索引使用的注意事项

 PRIMARY KEY 约束:唯一标识数据库表中的每条记录;

1主键必须包含唯一的值;

(2)主键列不能包含 NULL 值;

3每个表都应该有一个主键,并且每个表只能有一个主键。(PRIMARY KEY 拥有自动定义的 UNIQUE 约束)

 

1.2 说说反模式设计

谈反模式之前当然先要谈谈何为设计模式,因为两者是紧密联系在一起的。从我个人的理解认为,设计模式是一种在前人的设计经验上总结出来的对于一些普遍存在的问题提供的通用的解决方案。这些设计模式已经经过了长时间的实际应用和验证,被证实是有效可行的解决方案。

简单的来说,反模式是指在对经常面对的问题经常使用的低效,不良,或者有待优化的设计模式/方法。甚至,反模式也可以是一种错误的开发思想/理念。 在这里我举一个最简单的例子:在面向对象设计/编程中,有一条很重要的原则, 单一责任原则(Single responsibility principle)。其中心思想就是对于一个模块,或者一个类来说,这个模块或者这个类应该只对系统/软件的一个功能负责,而且该责任应该被该类完全封装起来。当开发人员需要修改系统的某个功能,这个模块/类是最主要的修改地方。相对应的一个反模式就是上帝类(God Class),通常来说,这个类里面控制了很多其他的类,同时也依赖其他很多类。整个类不光负责自己的主要单一功能,而且还负责了其他很多功能,包括一些辅助功能。很多维护老程序的开发人员们可能都遇过这种类,一个类里有几千行的代码,有很多功能,但是责任不明确单一。单元测试程序也变复杂无比。维护/修改这个类的时间要远远超出其他类的时间。很多时候,形成这种情况并不是开发人员故意的。很多情况下主要是由于随着系统的年限,需求的变化,项目的资源压力,项目组人员流动,系统结构的变化而导致某些原先小型的,符合单一原则类慢慢的变的臃肿起来。最后当这个类变成了维护的噩梦(特别是原先熟悉的开发人员离职后),重构该类就变成了一个不容易的工程。

 

1.3 说说分库与分表设计

https://www.cnblogs.com/sheseido/p/8880091.html

 

1.4 分库与分表带来的分布式困境与应对之策

 

1.5 说说SQL优化之道

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.

备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。

不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。


可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num = 0


3.应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。

4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or Name = 'admin'

可以这样查询:

select id from t where num = 10union allselect id from t where Name = 'admin'


5.in 和 not in 也要慎用,否则会导致全表扫描,如:

select id from t where num in(1,2,3)

对于连续的数值,能用 between 就不要用 in 了

select id from t where num between 1 and 3

很多时候用 exists 代替 in 是一个好的选择:

select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)

 

6.下面的查询也将导致全表扫描:

select id from t where name like ‘%abc%

若要提高效率,可以考虑全文检索。

7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num = @num

可以改为强制查询使用索引:

select id from t with(index(索引名)) where num = @num

.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2 = 100

应改为:

select id from t where num = 100*2


9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3) = ’abc’       -–name以abc开头的idselect id from t where datediff(day,createdate,’2005-11-30′) = 0    -–‘2005-11-30’    --生成的id

应改为:

select id from t where name like 'abc%'select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'


10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

12.不要写一些没有意义的查询,如需要生成一个空表结构:

select col1,col2 into #t from t where 1=0

这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)

13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。

14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。

15.select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。


16.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。

17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。

18.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

19.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

20.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段

21.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

22. 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。

23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

29.尽量避免大事务操作,提高系统并发能力。

30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

 

实际案例分析:拆分大的 DELETE 或INSERT 语句,批量提交SQL语句
  如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。
Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。
如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你的WEB服务崩溃,还可能会让你的整台服务器马上挂了。
  所以,如果你有一个大的处理,你一定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)条件是一个好的方法。下面是一个mysql示例:

while(1){

 

  //每次只做1000条

 

mysql_query(“delete from logs where log_date <= ’2012-11-01’ limit 1000”);

 

  if(mysql_affected_rows() == 0){

 

//删除完成,退出!

break;

}

 

//每次暂停一段时间,释放表让其他进程/线程访问。

usleep(50000)

 

}

 

1.6 MySQL 遇到的死锁问题

 

1.7 存储引擎InnoDB与MyISAM

简单来说,存储引擎就是指表的类型以及表在计算机上的存储方式。

存储引擎的概念是MySQL的特点,Oracle中没有专门的存储引擎的概念,Oracle有OLTP和OLAP模式的区分。不同的存储引擎决定了MySQL数据库中的表可以用不同的方式来存储。我们可以根据数据的特点来选择不同的存储引擎。

在MySQL中的存储引擎有很多种,可以通过“SHOW ENGINES”语句来查看。下面重点关注InnoDB、MyISAM、MEMORY这三种。

在MySQL中的存储引擎有很多种,可以通过“SHOW ENGINES”语句来查看。下面重点关注InnoDB、MyISAM、MEMORY这三种。

一.InnoDB存储引擎

InnoDB给MySQL的表提供了事务处理、回滚、崩溃修复能力和多版本并发控制的事务安全。在MySQL从3.23.34a开始包含InnnoDB。它是MySQL上第一个提供外键约束的表引擎。而且InnoDB对事务处理的能力,也是其他存储引擎不能比拟的。靠后版本的MySQL的默认存储引擎就是InnoDB。

 

InnoDB存储引擎总支持AUTO_INCREMENT。自动增长列的值不能为空,并且值必须唯一。MySQL中规定自增列必须为主键。在插入值的时候,如果自动增长列不输入值,则插入的值为自动增长后的值;如果输入的值为0或空(NULL),则插入的值也是自动增长后的值;如果插入某个确定的值,且该值在前面没有出现过,就可以直接插入。

 

InnoDB还支持外键(FOREIGN KEY)。外键所在的表叫做子表,外键所依赖(REFERENCES)的表叫做父表。父表中被字表外键关联的字段必须为主键。当删除、更新父表中的某条信息时,子表也必须有相应的改变,这是数据库的参照完整性规则。

 

InnoDB中,创建的表的表结构存储在.frm文件中(我觉得是frame的缩写吧)。数据和索引存储在innodb_data_home_dir和innodb_data_file_path定义的表空间中。

 

InnoDB的优势在于提供了良好的事务处理、崩溃修复能力和并发控制。缺点是读写效率较差,占用的数据空间相对较大。

 

二.MyISAM存储引擎

 

MyISAM是MySQL中常见的存储引擎,曾经是MySQL的默认存储引擎。MyISAM是基于ISAM引擎发展起来的,增加了许多有用的扩展。

 

MyISAM的表存储成3个文件。文件的名字与表名相同。拓展名为frm、MYD、MYI。其实,frm文件存储表的结构;MYD文件存储数据,是MYData的缩写;MYI文件存储索引,是MYIndex的缩写。

 

基于MyISAM存储引擎的表支持3种不同的存储格式。包括静态型、动态型和压缩型。其中,静态型是MyISAM的默认存储格式,它的字段是固定长度的;动态型包含变长字段,记录的长度不是固定的;压缩型需要用到myisampack工具,占用的磁盘空间较小。

 

MyISAM的优势在于占用空间小,处理速度快。缺点是不支持事务的完整性和并发性。

 

 

三.MEMORY存储引擎

 

MEMORY是MySQL中一类特殊的存储引擎。它使用存储在内存中的内容来创建表,而且数据全部放在内存中。这些特性与前面的两个很不同。

 

每个基于MEMORY存储引擎的表实际对应一个磁盘文件。该文件的文件名与表名相同,类型为frm类型。该文件中只存储表的结构。而其数据文件,都是存储在内存中,这样有利于数据的快速处理,提高整个表的效率。值得注意的是,服务器需要有足够的内存来维持MEMORY存储引擎的表的使用。如果不需要了,可以释放内存,甚至删除不需要的表。

 

MEMORY默认使用哈希索引。速度比使用B型树索引快。当然如果你想用B型树索引,可以在创建索引时指定。

 

注意,MEMORY用到的很少,因为它是把数据存到内存中,如果内存出现异常就会影响数据。如果重启或者关机,所有数据都会消失。因此,基于MEMORY的表的生命周期很短,一般是一次性的。

 

四.怎样选择存储引擎

 

在实际工作中,选择一个合适的存储引擎是一个比较复杂的问题。每种存储引擎都有自己的优缺点,不能笼统地说谁比谁好。

InnoDB:支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)。 

MyISAM:插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。如果应用的完整性、并发性要求比 较低,也可以使用。

MEMORY:所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,对数据的安全性要求较低,可以选择MEMOEY。它对表的大小有要求,不能建立太大的表。所以,这类数据库只使用在相对较小的数据库表。

注意,同一个数据库也可以使用多种存储引擎的表。如果一个表要求比较高的事务处理,可以选择InnoDB。这个数据库中可以将查询要求比较高的表选择MyISAM存储。如果该数据库需要一个用于查询的临时表,可以选择MEMORY存储引擎。

 

1.8 数据库索引的原理

(数据量大的情况下可以使用读写分离或者分表取解决)

目前Mysql的MyISAM和InnoDB都支持B-Tree索引,InnoDB还支持B+Tree索引,Memory还支持Hash.

常见的索引:哈希索引、B+树索引

索引的本质:MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。

 

 

 

1.9 为什么要用 B-tree

1.9.1 B+tree

其实B-Tree有许多变种,其中最常见的是B+Tree,比如MySQL就普遍使用B+Tree实现其索引结构。

二叉树、红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用B-/+Tree作为索引结构

目前Mysql的MyISAM和InnoDB都支持B-Tree索引,InnoDB还支持B+Tree索引,Memory还支持Hash

想要理解索引原理必须清楚一种数据结构「平衡树」(非二叉),也就是b tree或者 b+ tree,重要的事情说三遍:“平衡树,平衡树,平衡树”。当然, 有的数据库也使用哈希桶作用索引的数据结构,然而,主流的RDBMS都是把平衡树当做数据表默认的索引数据结构的。

关于叶子节点:叶子结点是离散数学中的概念。一棵树当中没有子结点(即度为0)的结点称为叶子结点,简称“叶子”。 叶子是指度为0的结点,又称为终端结点叶子结点 就是度为0的结点 就是没有子结点的结点

1.10 聚集索引与非聚集索引的区别

 

1.11 limit 20000 加载很慢怎么解决

 

1.12 选择合适的分布式主键方案

1、最简单的方法

 4台数据库,第一台mysql主键从1开始每次加4,第二台从2开始每次加4,以此类推。。

 

1.13 选择合适的数据存储方案

 

1.14 ObjectId 规则

 

1.15 聊聊 MongoDB 使用场景

 

1.16 倒排索引

 

1.17 聊聊 ElasticSearch 使用场景

 

2. 缓存使用

2.1 Redis 有哪些类型

(1)String类型

(2)list类型

(3)set类型

(4)sorted set 类型(有序集合)

(5)hash类型

 

2.2 Redis 内部结构

 

2.3 Redis 内存淘汰机制

 

2.4 聊聊 Redis 使用场景

 

2.5 Redis 持久化机制

 

2.6 Redis 集群方案与实现

 

2.7 Redis 为什么是单线程的

 

2.8 缓存崩溃

 

2.9 缓存降级

 

2.10 使用缓存的合理性问题

 

3. 消息队列

3.1 消息队列的使用场景

 

3.2 消息的重发补偿解决思路

 

3.3 消息的幂等性解决思路(已解答,待补充)

 

3.4 消息的堆积解决思路

 

3.5 自己如何实现消息队列

 

3.6 如何保证消息的有序性

 

三、框架篇

1. Spring

1.1 BeanFactory 和 ApplicationContext 有什么区别

BeanFactory:

是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;

 

ApplicationContext:

应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;

1) 国际化(MessageSource)

2) 访问资源,如URL和文件(ResourceLoader)

3) 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层  

4) 消息发送、响应机制(ApplicationEventPublisher)

5) AOP(拦截器)

 

1.2 Spring Bean 的生命周期

我们通常使用ApplicationContext作为Spring容器。这里,我们讲的也是 ApplicationContext中Bean的生命周期。而实际上BeanFactory也是差不多的,只不过处理器需要手动注册。

Spring框架中,一旦把一个Bean纳入Spring IOC容器之中,这个Bean的生命周期就会交由容器进行管理,一般担当管理角色的是BeanFactory或者ApplicationContext

 

1.3 Spring IOC 如何实现

 

1.4 说说 Spring AOP

 

Spring AOP 实现原理

 

动态代理(cglib 与 JDK)

 

Spring 事务实现方式

 

Spring 事务底层原理

 

如何自定义注解实现功能

 

Spring MVC 运行流程

 

Spring MVC 启动流程

 

Spring 的单例实现原理

 

Spring 框架中用到了哪些设计模式

 

Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)

 

2. Netty

为什么选择 Netty

 

说说业务中,Netty 的使用场景

 

原生的 NIO 在 JDK 1.7 版本存在 epoll bug

 

什么是TCP 粘包/拆包

 

TCP粘包/拆包的解决办法

 

Netty 线程模型

 

说说 Netty 的零拷贝

 

Netty 内部执行流程

 

Netty 重连实现

 

 

四、微服务篇

1. 微服务

1.1 前后端分离是如何做的

以往后台人员一边实现API接口,一边开发页面,两者互相切换着做,而且根据不同的url动态拼接页面,这也导致后台的开发压力大大增加。前后端工作分配不均。不仅仅开发效率慢,而且代码难以维护。而前后端分离的话,则可以很好的解决前后端分工不均的问题,将更多的交互逻辑分配给前端来处理,而后端则可以专注于其本职工作,比如提供API接口,进行权限控制以及进行运算工作。而前端开发人员则可以利用nodejs来搭建自己的本地服务器,直接在本地开发,然后通过一些插件来将api请求转发到后台,这样就可以完全模拟线上的场景,并且与后台解耦。前端可以独立完成与用户交互的整一个过程,两者都可以同时开工,不互相依赖,开发效率更快,而且分工比较均衡。

  1. 一般来说,要实现前后端分离,前端就需要开启一个本地的服务器来运行自己的前端代码,以此来模拟真实的线上环境,并且,也是为了更好的开发。因为你在实际开发中,你不可能要求每一个前端都去搭建一个java(php)环境,并且在java环境下开发,这对于前端来说,学习成本太高了。但如果本地没有开启服务器的话,不仅无法模拟线上的环境,而且还面临到了跨域的问题,因为你如果写静态的html页面,直接在文件目录下打开的话,你是无法发出ajax请求的(浏览器跨域的限制),因此,你需要在本地运行一个服务器,可是又不想搭建陌生而庞大的java环境,怎么办法呢?nodejs正好解决了这个问题。在我们项目中,我们利用nodejs的express框架来开启一个本地的服务器,然后利用nodejs的一个http-proxy-middleware插件将客户端发往nodejs的请求转发给真正的服务器,让nodejs作为一个中间层。这样,前端就可以无忧无虑的开发了
  2. 由于前后端分离后,前端和后台同时开发时,就可能遇到前端已经开发好一个页面了,可是却等待后台API接口的情况。比如说A是负责前端,B是负责后台,A可能用了一周做好了基本的结构,并且需要API接口联调后,才能继续开发,而此时B却还没有实现好所需要的接口,这种情况,怎么办呢?在我们这个项目里,我们是通过了mock来提供一些假数据,我们先规定好了API接口,设计出了一套API文档,然后我们就可以通过API文档,利用mock(http://mockjs.com)来返回一些假数据,这样就可以模拟发送API到接受响应的整一个过程,因此前端也不需要依赖于后端开发了,可以独立开发,等到后台的API全部设计完之后,就可以比较快速的联调。

 

 

 

1.2 如何解决跨域

方式一:使用ajax的jsonp

方式二:使用jQuery的jsonp插件

方式三:使用cors

 

1.3 微服务哪些框架

(1) SpringCloud

(2) Dubbo

 

1.4 你怎么理解RPC框架

谁能用通俗的语言解释一下什么是 RPC 框架? - 远程过程调用协议RPC(Remote Procedure Call Protocol)。

1.5 说说 RPC 的实现原理

1.6 说说Dubbo的实现原理

1.7 你怎么理解 RESTful

1.8 说说如何设计一个良好的 API

1.9 如何理解 RESTful API 的幂等性

1.10 如何保证接口的幂等性

1.11 说说 CAP 定理、 BASE 理论

1.12 怎么考虑数据一致性问题

1.13 说说最终一致性的实现方案

1.14 你怎么看待微服务

1.15 微服务与 SOA 的区别

1.16 如何拆分服务

1.17 微服务如何进行数据库管理

1.18 如何应对微服务的链式调用异常

1.19 对于快速追踪与定位问题

1.20 微服务的安全

2. 分布式

2.1 谈谈业务中使用分布式的场景

2.2 Session分布式方案

2.3 分布式锁的场景

2.4 分布是锁的实现方案

2.5 分布式事务

2.6 集群与负载均衡的算法与实现

2.7 说说分库与分表设计

2.8 分库与分表带来的分布式困境与应对之策

3. 安全问题

3.1 安全要素与 STRIDE 威胁

3.2 防范常见的 Web 攻击

3.3 服务端通信安全攻防

3.4 HTTPS原理剖析

3.5 HTTPS 降级攻击

3.6 授权与认证

3.7 基于角色的访问控制

3.8 基于数据的访问控制

4. 性能优化

4.1 性能指标有哪些

4.2 如何发现性能瓶颈

4.3 性能调优的常见手段

4.4 说说你在项目中如何进行性能调优

 

五、工程篇

1. 需求分析

  • 你如何对需求原型进行理解和拆分
  • 说说你对功能性需求的理解
  • 说说你对非功能性需求的理解
  • 你针对产品提出哪些交互和改进意见
  • 你如何理解用户痛点

 

2. 设计能力

  • 说说你在项目中使用过的 UML 图
  • 你如何考虑组件化
  • 你如何考虑服务化
  • 你如何进行领域建模
  • 你如何划分领域边界
  • 说说你项目中的领域建模
  • 说说概要设计

 

3. 设计模式

  • 你项目中有使用哪些设计模式
  • 说说常用开源框架中设计模式使用分析
  • 说说你对设计原则的理解
  • 23种设计模式的设计理念
  • 设计模式之间的异同,例如策略模式与状态模式的区别
  • 设计模式之间的结合,例如策略模式+简单工厂模式的实践
  • 设计模式的性能,例如单例模式哪种性能更好
  •  

4. 业务工程

  • 你系统中的前后端分离是如何做的
  • 说说你的开发流程
  • 你和团队是如何沟通的
  • 你如何进行代码评审
  • 说说你对技术与业务的理解
  • 说说你在项目中经常遇到的 Exception
  • 说说你在项目中遇到感觉最难Bug,怎么解决的
  • 说说你在项目中遇到印象最深困难,怎么解决的
  • 你觉得你们项目还有哪些不足的地方
  • 你是否遇到过 CPU 100% ,如何排查与解决
  • 你是否遇到过 内存 OOM ,如何排查与解决
  • 说说你对敏捷开发的实践
  • 说说你对开发运维的实践
  • 介绍下工作中的一个对自己最有价值的项目,以及在这个过程中的角色

 

5. 软实力

说说你的亮点

说说你最近在看什么书

说说你觉得最有意义的技术书籍

说说个人发展方向方面的思考

说说你认为的服务端开发工程师应该具备哪些能力

说说你认为的架构师是什么样的,架构师主要做什么

说说你所理解的技术专家

 

六、HR篇

你为什么离开之前的公司

你为什么要进我们公司

说说职业规划

你如何看待加班问题

谈一谈你的一次失败经历

你觉得你最大的优点是什么

你觉得你最大的缺点是什么

你在工作之余做什么事情

你为什么认为你适合这个职位

你觉得自己那方面能力最急需提高

你来我们公司最希望得到什么

你希望从这份工作中获得什么

你对现在应聘的职位有什么了解

您还有什么想问的

你怎么看待自己的职涯

谈谈你的家庭情况

你有什么业余爱好

你计划在公司工作多久

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值