精华内容
下载资源
问答
  • 2019第一篇文章-java的锁介绍(乐观锁与悲观锁)什么乐观锁、悲观锁乐观锁的CAS实现使用选择 什么乐观锁、悲观锁 第一次听到这个名字,怎么把锁都拟人化了?所以很好奇的去查阅了下,看了之后,发现这样比喻确实...

    2019第一篇文章-java的锁介绍(乐观锁与悲观锁)

    什么是乐观锁、悲观锁

    第一次听到这个名字,怎么把锁都拟人化了?所以很好奇的去查阅了下,看了之后,发现这样比喻确实有它的道理。对应到代码来说,就简单了。锁是java并发处理的一种实现方式,那我们来了解下这两种锁。
    乐观锁,总是很乐观的,在多线程并发过程中,不会认为别人每次会修改自己的数据,所以再别人操作时候不会上锁,如果是读取数据,那么直接返回数据,如果是更新数据,那么做一次判断,是否有别人更新,等待别人更新后再更新。那么这个很明显的优势就是,性能较高,不会因为每次上锁而降低性能,应用于高吞吐的操作。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(compare and set)实现的。AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)
    悲观锁,总是很悲观的,在多线程并发过程中,总是认为别人会侵犯自己,比如别人来读取或者修改数据,都会先上锁,当处理完成后再释放锁。如果是写,倒是还好,如果只是读取呢?那么势必会减低读取性能。这种锁独自占用资源,也叫做独占锁,在java中,使用关键字synchronized表示或者lock的就是这个类型。

    乐观锁的CAS实现

    CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS是非阻塞算法。
    以 java.util.concurrent 中的 AtomicInteger 为例,该类中原子操作保证了线程访问的准确性。
    getAndIncrement():获取数据
    compareAndSet(int expect, int update): 更新数据
    利用JNI(Java Native Interface)来完成CPU指令的操作,访问寄存器内存数据进行数据访问和设置。

    使用选择

    CAS与Synchronized的使用情景:

    1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

    2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

    补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

    展开全文
  •  结合框架体系应该最重要的如何灵活利用三种接口,set,map,list,他们如何遍历,各有什么特征,怎么样去处理,这关键,在这个基础上再去掌握在什么场合用什么类型的接口。比如说list和set,set无序的一个空袋子...

    锁(locking)

    业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算

    处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中

    (可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机

    制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓

    的“锁”,即给我们选定的目标数据上锁,使其无法被其他程序修改。

    hibernate支持两种锁机制:即通常所说的“悲观锁(Pessimistic Locking)”

    和“乐观锁(Optimistic Locking)”。

    悲观锁(Pessimistic Locking)

    悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自

    外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定

    状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能

    真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系

    统不会修改数据)。

    一个典型的倚赖数据库的悲观锁调用:

    select * from account where name=”Erica” for update

    这条sql 语句锁定了account 表中所有符合检索条件(name=”Erica”)的记录。

    本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。

    Hibernate的悲观锁,也是基于数据库的锁机制实现。

    下面的代码实现了对查询记录的加锁:

     

    String hqlStr =

    "from TUser as user where user.name='Erica'";

    Query query = session.createQuery(hqlStr);

    query.setLockMode("user",LockMode.UPGRADE); //加锁

    List userList = query.list();//执行查询,获取数据

    query.setLockMode对查询语句中,特定别名所对应的记录进行加锁(我们为

    TUser类指定了一个别名“user”),这里也就是对返回的所有user记录进行加锁。

    观察运行期Hibernate生成的SQL语句:

    select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id

    as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex

    from t_user tuser0_ where (tuser0_.name='Erica' ) for update

    这里Hibernate通过使用数据库的for update子句实现了悲观锁机制。

    Hibernate的加锁模式有:

    Ø LockMode.NONE : 无锁机制。

    Ø LockMode.WRITE :Hibernate在Insert和Update记录的时候会自动

    获取。

    Ø LockMode.READ : Hibernate在读取记录的时候会自动获取。

    以上这三种锁机制一般由Hibernate内部使用,如Hibernate为了保证Update

    过程中对象不会被外界修改,会在save方法实现中自动为目标对象加上WRITE锁。

    Ø LockMode.UPGRADE :利用数据库的for update子句加锁。

    Ø LockMode. UPGRADE_NOWAIT :Oracle的特定实现,利用oracle的for

    update nowait子句实现加锁。

    上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:

    Criteria.setLockMode

    Query.setLockMode

    Session.lock

    注意,只有在查询开始之前(也就是Hiberate 生成SQL 之前)设定加锁,才会

    真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含for update

    子句的Select SQL加载进来,所谓数据库加锁也就无从谈起。

    乐观锁(Optimistic Locking)

    相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依

    靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库

    性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

    如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进

    行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过

    程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作

    员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几

    百上千个并发,这样的情况将导致怎样的后果。

    乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本

    (Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于

    数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来

    实现。

    读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提

    交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据

    版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

    对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个

    version字段,当前值为1;而当前帐户余额字段(balance)为$100。

    1 操作员A 此时将其读出(version=1),并从其帐户余额中扣除$50

    ($100-$50)。

    2 在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并

    从其帐户余额中扣除$20($100-$20)。

    3 操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户扣

    除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大

    于数据库记录当前版本,数据被更新,数据库记录version更新为2。

    4 操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数

    据(balance=$80),但此时比对数据库记录版本时发现,操作员B提交的

    数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记

    录当前版本才能执行更新“的乐观锁策略,因此,操作员B 的提交被驳回。

    这样,就避免了操作员B 用基于version=1 的旧数据修改的结果覆盖操作

    员A的操作结果的可能。

    从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员A

    和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系

    统整体性能表现。

    需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局

    限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户

    余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在

    系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如

    将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途

    径,而不是将数据库表直接对外公开)。

    Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数

    据库的更新操作,利用Hibernate提供的透明化乐观锁实现,将大大提升我们的

    生产力。

    Hibernate中可以通过class描述符的optimistic-lock属性结合version

    描述符指定。

    现在,我们为之前示例中的TUser加上乐观锁机制。

    1. 首先为TUser的class描述符添加optimistic-lock属性:

    <hibernate-mapping>

    <class

    name="org.hibernate.sample.TUser"

    table="t_user"

    dynamic-update="true"

    dynamic-insert="true"

    optimistic-lock="version"

    >

    ……

    </class>

    </hibernate-mapping>

    optimistic-lock属性有如下可选取值:

    Ø none

    无乐观锁

    Ø version

    通过版本机制实现乐观锁

    Ø dirty

    通过检查发生变动过的属性实现乐观锁

    Ø all

    通过检查所有属性实现乐观锁

    其中通过version实现的乐观锁机制是Hibernate官方推荐的乐观锁实现,同时也

    是Hibernate中,目前唯一在数据对象脱离Session发生修改的情况下依然有效的锁机

    制。因此,一般情况下,我们都选择version方式作为Hibernate乐观锁实现机制。

    2. 添加一个Version属性描述符

    <hibernate-mapping>

    <class

    name="org.hibernate.sample.TUser"

    table="t_user"

    dynamic-update="true"

    dynamic-insert="true"

    optimistic-lock="version"

    >

    <id

    name="id"

    column="id"

    type="Java.lang.Integer"

    >

    <generator class="native">

    </generator>

    </id>

    <version

    column="version"

    name="version"

    type="java.lang.Integer"

    />

    ……

    </class>

    </hibernate-mapping>

    注意version 节点必须出现在ID 节点之后。

    这里我们声明了一个version属性,用于存放用户的版本信息,保存在TUser表的

    version字段中。

    此时如果我们尝试编写一段代码,更新TUser表中记录数据,如:

    Criteria criteria = session.createCriteria(TUser.class);

    criteria.add(Expression.eq("name","Erica"));

    List userList = criteria.list();

    TUser user =(TUser)userList.get(0);

    Transaction tx = session.beginTransaction();

    user.setUserType(1); //更新UserType字段

    tx.commit();

    每次对TUser进行更新的时候,我们可以发现,数据库中的version都在递增。

    而如果我们尝试在tx.commit 之前,启动另外一个Session,对名为Erica 的用

    户进行操作,以模拟并发更新时的情形:

    Session session= getSession();

    Criteria criteria = session.createCriteria(TUser.class);

    criteria.add(Expression.eq("name","Erica"));

    Session session2 = getSession();

    Criteria criteria2 = session2.createCriteria(TUser.class);

    criteria2.add(Expression.eq("name","Erica"));

    List userList = criteria.list();

    List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);

    TUser user2 =(TUser)userList2.get(0);

    Transaction tx = session.beginTransaction();

    Transaction tx2 = session2.beginTransaction();

    user2.setUserType(99);

    tx2.commit();

    user.setUserType(1);

    tx.commit();

    执行以上代码,代码将在tx.commit()处抛出StaleObjectStateException异

    常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我

    们就可以在乐观锁校验失败时进行相应处理。


    展开全文
  • 之所以要防止多线程同时操作同一条数据导致数据混乱,错乱,如同一家银行的两张信用卡(信用卡信用机制),此时两张信用额度一致的,总共1000元,不是所说的办理两张信用卡就2000元,扯得有点远,这个时候 ...

    之所以要锁,是防止多线程同时操作同一条数据导致数据混乱,错乱,如同一家银行的两张信用卡(信用卡信用机制),此时两张信用额度是一致的,总共1000元,不是所说的办理两张信用卡就2000元,扯得有点远,这个时候 甲乙同时消费100元,那么数据是怎么处理的呢?拿出1000 减100?那就成900 另外100元呢?这个时候就需要使用锁,锁住该条数据。如下解释

    锁(locking)

    业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算

    处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中

    (可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机

    制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓

    的“锁”,即给我们选定的目标数据上锁,使其无法被其他程序修改。

    Hibernate支持两种锁机制:即通常所说的“悲观锁(Pessimistic Locking)”和“乐观锁(Optimistic Locking)”。

    悲观锁(Pessimistic Locking)

    悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自

    外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定

    状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能

    真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系

    统不会修改数据)。

    一个典型的倚赖数据库的悲观锁调用:

    select * from account where name=”Erica” for update

    这条sql 语句锁定了account 表中所有符合检索条件(name=”Erica”)的记录。

    本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。

    Hibernate的悲观锁,也是基于数据库的锁机制实现。

    下面的代码实现了对查询记录的加锁:

     

    String hqlStr =

    "from TUser as user where user.name='Erica'";

    Query query = session.createQuery(hqlStr);

    query.setLockMode("user",LockMode.UPGRADE); //加锁

    List userList = query.list();//执行查询,获取数据

    query.setLockMode对查询语句中,特定别名所对应的记录进行加锁(我们为

    TUser类指定了一个别名“user”),这里也就是对返回的所有user记录进行加锁。

    观察运行期Hibernate生成的SQL语句:

    select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id

    as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex

    from t_user tuser0_ where (tuser0_.name='Erica' ) for update

    这里Hibernate通过使用数据库的for update子句实现了悲观锁机制。

    Hibernate的加锁模式有:

    Ø LockMode.NONE : 无锁机制。

    Ø LockMode.WRITE :Hibernate在Insert和Update记录的时候会自动

    获取。

    Ø LockMode.READ : Hibernate在读取记录的时候会自动获取。

    以上这三种锁机制一般由Hibernate内部使用,如Hibernate为了保证Update

    过程中对象不会被外界修改,会在save方法实现中自动为目标对象加上WRITE锁。

    Ø LockMode.UPGRADE :利用数据库的for update子句加锁。

    Ø LockMode. UPGRADE_NOWAIT :Oracle的特定实现,利用Oracle的for

    update nowait子句实现加锁。

    上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:

    Criteria.setLockMode

    Query.setLockMode

    Session.lock

    注意,只有在查询开始之前(也就是Hiberate 生成SQL 之前)设定加锁,才会

    真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含for update

    子句的Select SQL加载进来,所谓数据库加锁也就无从谈起。

     

    乐观锁(Optimistic Locking)

    相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依

    靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库

    性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

    如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进

    行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过

    程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作

    员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几

    百上千个并发,这样的情况将导致怎样的后果。

    乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本

    (Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于

    数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来

    实现。

    读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提

    交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据

    版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

    对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个

    version字段,当前值为1;而当前帐户余额字段(balance)为$100。

    1 操作员A 此时将其读出(version=1),并从其帐户余额中扣除$50

    ($100-$50)。

    2 在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并

    从其帐户余额中扣除$20($100-$20)。

    3 操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户扣

    除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大

    于数据库记录当前版本,数据被更新,数据库记录version更新为2。

    4 操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数

    据(balance=$80),但此时比对数据库记录版本时发现,操作员B提交的

    数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记

    录当前版本才能执行更新“的乐观锁策略,因此,操作员B 的提交被驳回。

    这样,就避免了操作员B 用基于version=1 的旧数据修改的结果覆盖操作

    员A的操作结果的可能。

    从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员A

    和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系

    统整体性能表现。

    需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局

    限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户

    余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在

    系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如

    将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途

    径,而不是将数据库表直接对外公开)。

    Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数

    据库的更新操作,利用Hibernate提供的透明化乐观锁实现,将大大提升我们的

    生产力。

    Hibernate中可以通过class描述符的optimistic-lock属性结合version

    描述符指定。

    现在,我们为之前示例中的TUser加上乐观锁机制。

    1. 首先为TUser的class描述符添加optimistic-lock属性:

    <hibernate-mapping>

    <class name="org.hibernate.sample.TUser"

    table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version">

    ……

    </class>

    </hibernate-mapping>

    optimistic-lock属性有如下可选取值:

    Ø none

    无乐观锁

    Ø version

    通过版本机制实现乐观锁

    Ø dirty

    通过检查发生变动过的属性实现乐观锁

    Ø all

    通过检查所有属性实现乐观锁

    其中通过version实现的乐观锁机制是Hibernate官方推荐的乐观锁实现,同时也

    是Hibernate中,目前唯一在数据对象脱离Session发生修改的情况下依然有效的锁机

    制。因此,一般情况下,我们都选择version方式作为Hibernate乐观锁实现机制。

    2. 添加一个Version属性描述符

    <hibernate-mapping>

    <class name="org.hibernate.sample.TUser"

    table="t_user" dynamic-update="true" dynamic-insert="true"

    optimistic-lock="version">

    <id name="id" column="id" type="java.lang.Integer">

    <generator class="native">

    </generator>

    </id>

    <version column="version" name="version" type="java.lang.Integer"/>

    ……

    </class>

    </hibernate-mapping>

    注意version 节点必须出现在ID 节点之后。

    这里我们声明了一个version属性,用于存放用户的版本信息,保存在TUser表的

    version字段中。

    此时如果我们尝试编写一段代码,更新TUser表中记录数据,如:

    Criteria criteria = session.createCriteria(TUser.class);

    criteria.add(Expression.eq("name","Erica"));

    List userList = criteria.list();

    TUser user =(TUser)userList.get(0);

    Transaction tx = session.beginTransaction();

    user.setUserType(1); //更新UserType字段

    tx.commit();

    每次对TUser进行更新的时候,我们可以发现,数据库中的version都在递增。

    而如果我们尝试在tx.commit 之前,启动另外一个Session,对名为Erica 的用

    户进行操作,以模拟并发更新时的情形:

    Session session= getSession();

    Criteria criteria = session.createCriteria(TUser.class);

    criteria.add(Expression.eq("name","Erica"));

    Session session2 = getSession();

    Criteria criteria2 = session2.createCriteria(TUser.class);

    criteria2.add(Expression.eq("name","Erica"));

    List userList = criteria.list();

    List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);

    TUser user2 =(TUser)userList2.get(0);

    Transaction tx = session.beginTransaction();

    Transaction tx2 = session2.beginTransaction();

    user2.setUserType(99);

    tx2.commit();

    user.setUserType(1);

    tx.commit();

    执行以上代码,代码将在tx.commit()处抛出StaleObjectStateException异

    常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我

    们就可以在乐观锁校验失败时进行相应处理。

    如以上示例,其中会有一个人消费不成功失败,重新进行消费。

    展开全文
  • Java中的一些常问问题

    2020-09-20 17:21:59
    Java问题整理数据库索引的定义与使用索引是怎么加快查询速率的数据库中悲观锁和乐观锁的代码实现Arraylist 的实现一百万个对象存入arraylist要怎么处理线程池怎么实现,工作流程饱和策略HashSet是怎么确保元素不重复...

    数据库索引的定义与使用

    索引定义:

    • 索引是为了加速对表中数据行的检索而创建的一种分散的存储结构。索引是针对表而建立的,它是由数据页面以外的索引页面组成的,每个索引页面中的行都会含有逻辑指针,以便加速检索物理数据。
    • SQL Server 允许用户在表中创建索引,指定按某列预先排序,从而大大提高查询速度。类似于汉语词典中按照拼音或字画查找。

    索引作用:

    通过索引可以大大地提高数据库的检索速度,提高数据库的性能。

    索引的类型:

    • 唯一索引:唯一索引不允许两行有相同的索引值。即唯一索引这一列,每个值都是唯一的
      例如:在 user 表中,user 的 用户名(uname)列上创建了唯一索引,则所有用户的用户名不能重复。

    • 主键索引:定义表主键的时候,会自动创建主键索引(主键索引是唯一索引的特例),主键索引要求每一个值都是唯一且非空
      主键索引是唯一索引的特殊类型。主键索引要求主键中的每个值都是唯一的,当在查询中使用主键索引时,允许快速访问数据。

    • 聚集索引:表中各行的物理顺序与键值的逻辑顺序相同,每个表只有一个
      在聚集索引中,表中各行的物理顺序与键值的逻辑(索引)顺序相同。表中只能包含一个聚集索引。
      例如:汉语字典默认按拼音排序编排字典中的每页页码。拼音字母 a,b,c,…,x,y,z 就是索引的逻辑顺序,而页码1,2,3…就是物理顺序。默认按拼音排序的字典其索引顺序和逻辑顺序是一致的。拼音顺序越后的字对应的页码也越大(ha对应的页码比 ba 页码靠后)。

    • 非聚集索引:非聚集索引指定表的逻辑顺序,数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针
      如果不是聚集索引,表中各行的物理顺序与键值逻辑顺序不匹配。聚集索引比非聚集索引有更快的数据访问速度。
      例如:按笔画顺序的索引就是非聚集索引,“1”画的字(词)对应的页码可能比“3”画的字(词)对应的页码大(靠后)。
      注意:SQL Server中,一个表只能创建1个聚集索引,多个非聚集索引。设置某列为主键,该列就默认为聚集索引。

    索引的优缺点:

    • 优点:加快访问速度,将强行的唯一性
    • 缺点:带索引的表在数据库中的存储需要更多的空间

    适合场景:

    • 该列频繁用于搜索
    • 该列用于对数据进行排序
      在这里插入图片描述

    索引操作

    • 建立索引
     create index 索引名 on 表名(索引字段名)
    
    • 建立唯一索引
    CREATE UNIQUE INDEX <索引的名字> ON tablename (列的列表)
    //修改表
    ALTER TABLE tablename ADD UNIQUE [索引的名字] (列的列表); 
    
    //创建表的时候指定索引
    CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (列的列表) )
    • 建立复合索引
    create index 索引名 on 表名(索引字段名1,索引字段名2...
    • 查看表中索引
    show index from 表名
    
    • 删除索引
    drop index 索引名 on 表名
    
    • 查看索引是否命中
      Mysql:结果 type=index 表示命中
    explain +sql语句
    

    索引是怎么加快查询速率的

    • 索引就是通过事先排好序,从而在查找时可以应用二分查找等高效率的算法。一般的顺序查找,复杂度为O(n),而二分查找复杂度为O(log2n)。当n很大时,二者的效率相差及其悬殊。
    • 索引和表都是以数据块的形式存储,但是二者存储结构不同。索引是b-tree结构的一组有序的数据,里面存放的是 key值和行地址(oracle 是rowid)。比如一张表中id字段,有1-100 行,查询id=5这行,那么只需要遍历B-tree树,找到5这个节点,就可以确定一行。但是如果没有索引,就需要遍历整个表的数据块,来确定id=5的这一行。遍历有序的b-tree数据块,确定此行所在位置之后,再一次读取数据,比扫描表所有数据块要快很多

    使用例子

    在条件中加大于,小于或者等于会影响索引的命中和查询效率吗?

    • 等于肯定会使用索引,但是小于,大于就不一定

    字符串可以加索引吗?

    可以

    • 使用前缀索引
    • 倒序存储
    • 使用 hash 字段
      第二,第三占用空间差不多,第二 CPU 消耗较小,第三查询效率好

    参考文章

    like模糊查询,前缀,中间,后缀查询会走索引吗?那种最快?(待)

    • like 模糊查询
      1、模糊查询,后通配 走索引 ,前通配 走全表—— *% 或 % *
      2、where条件用in或or 不会走索引
      3、order by 排序时,如果碰到相同的值,则会随机进行排序,排序出来的结果集不是固定的。建议使用id序列进行排序(唯一的值)。

    sql 关键字 explain

    explain显示了mysql如何使用索引来处理select语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。

    使用方法,在select语句前加上explain就可以了:

    explain select surname,first_name form a,b where a.id=b.id 
    

    参考文章

    数据库中悲观锁和乐观锁的代码实现

    • 悲观锁:就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。【数据锁定:数据将暂时不会得到修改】
      • 排它锁,当事务在操作数据时把这部分数据进行锁定,直到操作完毕后再解锁,其他事务操作才可操作该部分数据。这将防止其他进程读取或修改表中的数据。
      • 大多数情况下依靠数据库的锁机制实现
      • 例如select * from account where name=”Max” for update;
        这条sql 语句锁定了account 表中所有符合检索条件(name=”Max”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
    • 乐观锁:认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息。让用户决定如何去做。
      • 如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户重新操作。
      • 大多数基于数据版本(Version)记录机制实现
      • update task set value = newValue, version = versionValue + 1 where version = versionValue;
      • 代码参考
      • 相关文章

    Arraylist 的实现

    ArrayList是List接口的可变数组的实现,底层使用数组保存所有元素。其操作基本上是对数组的操作。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。

    • 底层使用数组
    private transient Object[] elementData;  
    
    • 构造方法
    //构造一个默认初始容量为10的空列表
    public ArrayList() {  
        this(10);  
    }  
    
    //构造一个指定初始容量的空列表
    public ArrayList(int initialCapacity) {  
        super();  
        if (initialCapacity < 0)  
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);  
        this.elementData = new Object[initialCapacity];  
    }  
      
    //构造一个包含指定 collection 的元素的列表
    public ArrayList(Collection<? extends E> c) {  
        elementData = c.toArray();  
        size = elementData.length;  
        // c.toArray might (incorrectly) not return Object[] (see 6260652)  
        if (elementData.getClass() != Object[].class)  
            elementData = Arrays.copyOf(elementData, size, Object[].class);  
    }  
    
    • 存储
    // 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。  
    public E set(int index, E element) {  
        RangeCheck(index);  
      
        E oldValue = (E) elementData[index];  
        elementData[index] = element;  
        return oldValue;  
    }  
    
    // 将指定的元素添加到此列表的尾部。  
    public boolean add(E e) {  
        ensureCapacity(size + 1);   
        elementData[size++] = e;  
        return true;  
    }  
    
    // 将指定的元素插入此列表中的指定位置。  
    // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。  
    public void add(int index, E element) {  
        if (index > size || index < 0)  
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  
        // 如果数组长度不足,将进行扩容。  
        ensureCapacity(size+1);  // Increments modCount!!  
        // 将 elementData中从Index位置开始、长度为size-index的元素,  
        // 拷贝到从下标为index+1位置开始的新的elementData数组中。  
        // 即将当前位于该位置的元素以及所有后续元素右移一个位置。  
        System.arraycopy(elementData, index, elementData, index + 1, size - index);  
        elementData[index] = element;  
        size++;  
    }  
    
    // 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。  
    public boolean addAll(Collection<? extends E> c) {  
        Object[] a = c.toArray();  
        int numNew = a.length;  
        ensureCapacity(size + numNew);  // Increments modCount  
        System.arraycopy(a, 0, elementData, size, numNew);  
        size += numNew;  
        return numNew != 0;  
    }  
    
    // 从指定的位置开始,将指定collection中的所有元素插入到此列表中。  
    public boolean addAll(int index, Collection<? extends E> c) {  
        if (index > size || index < 0)  
            throw new IndexOutOfBoundsException(  
                "Index: " + index + ", Size: " + size);  
      
        Object[] a = c.toArray();  
        int numNew = a.length;  
        ensureCapacity(size + numNew);  // Increments modCount  
      
        int numMoved = size - index;  
        if (numMoved > 0)  
            System.arraycopy(elementData, index, elementData, index + numNew, numMoved);  
      
        System.arraycopy(a, 0, elementData, index, numNew);  
        size += numNew;  
        return numNew != 0;  
    }  
    
    • 读取
    // 返回此列表中指定位置上的元素。  
    public E get(int index) {  
        RangeCheck(index);  
      
        return (E) elementData[index];  
    }  
    
    • 删除
    // 移除此列表中指定位置上的元素。  
    public E remove(int index) {  
        RangeCheck(index);  
      
        modCount++;  
        E oldValue = (E) elementData[index];  
      
        int numMoved = size - index - 1;  
        if (numMoved > 0)  
            System.arraycopy(elementData, index+1, elementData, index, numMoved);  
        elementData[--size] = null; // Let gc do its work  
      
        return oldValue;  
    }  
    
    // 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。  
    public boolean remove(Object o) {  
        // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。  
        if (o == null) {  
            for (int index = 0; index < size; index++)  
                if (elementData[index] == null) {  
                    // 类似remove(int index),移除列表中指定位置上的元素。  
                    fastRemove(index);  
                    return true;  
                }  
    } else {  
        for (int index = 0; index < size; index++)  
            if (o.equals(elementData[index])) {  
                fastRemove(index);  
                return true;  
            }  
        }  
        return false;  
    }  
    

    注意:从数组中移除元素的操作,也会导致被移除的元素以后的所有元素向左移动一个位置

    • 调整数组容量
    public void ensureCapacity(int minCapacity) {  
        modCount++;  
        int oldCapacity = elementData.length;  
        if (minCapacity > oldCapacity) {  
            Object oldData[] = elementData;  
            int newCapacity = (oldCapacity * 3)/2 + 1;  
                if (newCapacity < minCapacity)  
                    newCapacity = minCapacity;  
          // minCapacity is usually close to size, so this is a win:  
          elementData = Arrays.copyOf(elementData, newCapacity);  
        }  
    }  
    
    • 每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。

    • 数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。

    • 数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

    • 将底层数组的容量调整为当前列表保存的实际元素的大小—trimToSize

    public void trimToSize() {  
        modCount++;  
        int oldCapacity = elementData.length;  
        if (size < oldCapacity) {  
            elementData = Arrays.copyOf(elementData, size);  
        }  
    }  
    

    ArrayList 是线程不安全,有哪些 list 是安全的

    常见集合

    在这里插入图片描述

    线程不安全

    当多个并发同时对非线程安全的集合进行增删改的时候会破坏这些集合的数据完整性;
    例如:当多个线程访问同一个集合或Map时,如果有超过一个线程修改了ArrayList集合,则程序必须手动保证该集合的同步性。

    • Vector、HashTable、Properties是线程安全的

    • ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的

    • 为了保证集合是线程安全的,相应的效率也比较低;线程不安全的集合效率相对会高一些。

    Vector -> SynchronizedList -> CopyOnWriteArrayList

    • Vector
      每个方法都添加了synchronized关键字来保证同步,所以它是线程安全的,但是正是这些方法的同步,让其效率大大的降低了。比ArrayList的效率要慢。
      源码解析
    • SynchronizedList
      除了使用 Vector 来代替 ArrayList 线程安全问题,还可以使用:
    java.util.Collections.SynchronizedList
    

    它能把所有 List 接口的实现类转换成线程安全的 List,比 Vector 有更好的扩展性和兼容性,其构造方法如下

    final List<E> list;
    
    SynchronizedList(List<E> list){
    	super(list);
    	this.list = list;
    }
    

    Synchronized 部分方法

    public E get(int index) {
        synchronized (mutex) {return list.get(index);}
    }
    public E set(int index, E element) {
        synchronized (mutex) {return list.set(index, element);}
    }
    public void add(int index, E element) {
        synchronized (mutex) {list.add(index, element);}
    }
    public E remove(int index) {
        synchronized (mutex) {return list.remove(index);}
    }
    

    有代码可以看出,方法都带同步对象锁,性能不是最优。下面介绍更合适的解决方案

    java.util.concurrent.CopyOnWriteArrayList
    java.util.concurrent.CopyOnWriteArraySet
    

    !!!这两种并发集合,只适合于读多写少的情况,如果写多读少,使用这个就没意义了,因为每次写操作都要进行集合内存复制,性能开销很大,如果集合较大,很容易造成内存溢出。

    • CopyOnWriteArrayList
      CopyOnWrite(简称:COW):即复制再写入,就是在添加元素的时候,先把原 List 列表复制一份,再添加新的元素。

    add方法源码
    添加元素时,先加锁,再进行复制替换操作,最后释放锁

    public boolean add(E e) {
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 获取原始集合
            Object[] elements = getArray();
            int len = elements.length;
    
            // 复制一个新集合
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
    
            // 替换原始集合为新集合
            setArray(newElements);
            return true;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    

    get 方法源码

    private E get(Object[] a, int index) {
        return (E) a[index];
    }
    
    public E get(int index) {
        return get(getArray(), index);
    }
    

    获取元素并没有加锁:好处是在高并发的情况下,读取元素时无锁,写数据才加锁,大大提升了读取性能

    • CopyOnWriteArraySet
      就是使用 CopyOnWriteArrayList 的 addIfAbsent 方法来去重的,添加元素的时候判断对象是否已经存在,不存在才添加进集合。
    /**
     * Appends the element, if not present.
     *
     * @param e element to be added to this list, if absent
     * @return {@code true} if the element was added
     */
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
    

    一百万个对象存入arraylist要怎么处理

    • 使用多线程,将 ArrayList 划分为多个片,每个线程搜索一个片,最后将结果集合起来(使用CPU核心数2倍数量线程)
    • 使用 fork-join框架——任务偷取算法
      参考代码
      fork-join框架

    (类似插入多条数量:因为ArrayList的底层是数组实现,并且数组的默认值是10,如果插入10000条要不断的扩容,耗费时间,所以我们调用ArrayList的指定容量的构造器方法ArrayList(int initialCapacity) 就可以实现不扩容,就提高了性能。)

    线程池怎么实现,工作流程

    • 线程池的主要处理流程
      在这里插入图片描述

      • 判断核心线程池是否已满(都有任务),若不是则创建一个新的工作线程来执行新任务;否则判断盘对工作队列是否已满
      • 判断工作队列是否已满,若不是则将提交的新任务加入工作队列中;若工作队列已满,则判断线程池是否已满
      • 判断线程池是否已满,若不是则创建一个新的线程来执行新任务;否则交给饱和策略来处理这个任务
    • 线程池源码

      • ThreadPoolExecutor的execute()方法
    public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
           //如果线程数大于等于基本线程数或者线程创建失败,将任务加入队列
            if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
              //线程池处于运行状态并且加入队列成功
              if (runState == RUNNING && workQueue.offer(command)) {
                   if (runState != RUNNING || poolSize == 0)
                         ensureQueuedTaskHandled(command);
                 }
             //线程池不处于运行状态或者加入队列失败,则创建线程(创建的是非核心线程)
                else if (!addIfUnderMaximumPoolSize(command))
               //创建线程失败,则采取阻塞处理的方式
                    reject(command); // is shutdown or saturated
            }
        }
    
    • 线程池代码实现的注意点
      • 不推荐使用jdk自带的executors的方式来创建线程池——阿里巴巴java开发手册中明确规定不允许使用Executors创建线程池。
        即:
        FixedThreadPool,
        SingleThreadPoo,
        CacheThreadPool,
        ScheduledThreadPool.
        在这里插入图片描述

      • 创建线程池的正确方式
        避免使用 Executors 创建线程池:主要避免了其中的默认实现,可以改用 ThreadPoolExecutor构造方法指定参数
        在这里插入图片描述
        需要指定核心线程池的大小、最大线程池的数量、保持存活的时间、等待队列容量的大小。在这种情况下一旦提交的线程数超过当前可用的线程数时就会抛出拒绝执行的异常 java.util.concurrent.RejectedExecutionException 有界队列已经满了便无法处理新的任务。

      • 使用工具类来创建线程池:
        apache guava(不仅可以避免OOM的问题,还可以自定义线程名称,更加方便出错时溯源)等
        在这里插入图片描述
        刚开始都是在创建新的线程,达到核心线程数量5个后,新的任务进来后不再创建新的线程,而是将任务加入工作队列,任务队列到达上线5个后,新的任务又会创建新的普通线程,直到达到线程池最大的线程数量10个,后面的任务则根据配置的饱和策略来处理。

    线程池的参数——7个

    corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler

    • corePoolSize 线程池核心线程大小

    • maximumPoolSize 线程池最大线程数量

    • keepAliveTime 空闲线程存活时间

    一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

    • unit 空间线程存活时间单位:keepAliveTime的计量单位

    • workQueue 工作队列

    新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

    ①ArrayBlockingQueue

    基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

    ②LinkedBlockingQuene

    基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

    ③SynchronousQuene

    一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

    ④PriorityBlockingQueue

    具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

    • threadFactory 线程工厂

    创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

    • handler 拒绝策略/饱和策略

    饱和策略

    RejectedExecutionHandler:饱和策略
    当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:

    1、AbortPolicy:直接抛出异常(默认)

    2、CallerRunsPolicy:只用调用所在的线程运行任务

    3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

    4、DiscardPolicy:不处理,丢弃掉。

    参考文章1
    参考文章2
    参考文章3

    数据库连接池应该设置多大

    • 数据库连接数 maxActive 与下列有关
      • cpu
      • 磁盘 IO
      • 网络 IO
    • 数据库连接池的大小最合适设置为:((核心数 * 2)+ 有效磁盘数)

    HashSet是怎么确保元素不重复

    HashSet是哈希表结构,当一个元素要存入HashSet集合时,首先通过自身的hashCode方法算出一个值,然后通过这个值查找元素在集合中的位置,如果该位置没有元素,那么就存入。如果该位置上有元素,那么继续调用该元素的equals方法进行比较,如果equals方法返回为真,证明这两个元素是相同元素,则不存。否则则在该位置上存储2个元素(一般不可能重复)所以当一个自定义的对象想正确存入HashSet集合,那么应该重写Object的hashCode和equals。HashSet是通过hashCode()和equals()方法确保元素无序不重复的 。

    上下文切换是什么

    • 多线程编程中一遍线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
    • 概括来说:当前任务在执行完 CPU 分配给自己的时间片要切换到另一个任务之前会先保存自己的状态,以便下次再切换回当前这个任务时,可以重新加载这个任务的状态任务从保存到再加载的过程就是一次上下文切换。

    Java内存中哪些会产生溢出

    常见原因

    • 内存中加载的数据量过于庞大,如一次从数据库取出大量过多数据
    • 集合类中有对 对象 的引用,使用完未清空,使得 JVM 不能回收
    • 代码中存在死循环或循环产生过多重复的对象实体
    • 使用的第三方软件中的bug
    • 启动参数内存值设置过小

    解决方案

    • 修改 JVM 启动参数,直接增加内存(-Xms参数必须加上)
      • 检查错误日志,查看“OutOfMemory”错误前是否有其他异常或错误
    • 对代码进行走查分析,找出可能发生内存溢出的位置,重点如下
      • 检查对数据库的查询中,是否有一次获得全部数据的查询。(对于数据库查询尽量采用分页的方式查询)
      • 检查代码中是否出现死循环或递归调用
      • 检查是否大循环重复产生新对象实体
      • 检查List,Map等集合对象是否有使用完毕后,未清除的问题。List,Map 等集合对象会始终有对 对象 的引用,不能被 GC 回收
        Java底层GC
    • 使用内存查看工具动态查看内存使用情况

    Java缓存应用

    为什么 Java 中 “1000 == 1000” 为false,而 “100 == 100” 为true

        Integer a = 1000, b = 1000;
        System.out.println(a == b);//1
        Integer c = 100, d = 100;
        System.out.println(c == d);//2
    

    结果

    false
    true
    

    如果两个引用指向同一个对象,用 == 他们是相等的。如果两个引用指向不同的对象,用 == 他们是不相等的,即使他们内容相同。

    原因:Integer.java 类中有一个内部私有类 IntegerCache.java。其中缓存了从 -128 到 127 之间的所有整数对象。
    所以所有的小整数在内部缓存,当我们声明类似

    Integer c = 100;
    

    实际上内部处理

    Integer i = Integer.valueOf(100);
    

    观察 valueOf()方法

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

    所以当值的范围在 -128到127 之间,他就从高速缓存返回实例。即下面代码指向同一个对象

    Integer c = 100, d = 100;
    System.out.println(c == d);
    

    接口与抽象类的区别

    在这里插入图片描述

    • 抽象类和接口都不能被直接实例化,子类必须实现了这个其定义的所有的方法
    • 抽象类要被子类继承,接口要被子类实现
    • 接口里面只能对方法进行声明,抽象类既可以对方法进行声明也可以对方法进行实现,抽象类还可以有构造方法
    • 抽象类里面可以没有抽象方法,如果一个类里面有抽象方法,那么这个类一定是抽象类
    • 抽象类中的方法都要被实现,所以抽象方法不能是静态的static,也不能是私有的private
    • 接口中只能够有静态的不能被修改的数据成员(也就是必须是 static final的,不过在 interface中一般不定义数据成员);抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值
    • 接口(类)可以继承接口,甚至可以继承多个接口。但是类只能继承一个类
    • 抽象类主要是用来抽象类别,接口主要是用来抽象方法功能
      * 当你关注事物的本质的时候,请用抽象类;当你关注一种操作的时候,用接口

    MyBatis与Hibernate区别

    Spring Data JPA可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。JPA 和 Mybatis 的区别也就是 Hibernate和mybatis的区别了。

    相同点

    • 两者生成 Session 的过程及 Session 的生命周期相似
    • 两者都支持 JDBC 和 JTA 事务
      • JTA:解决多数据源和分布式事务问题
        JTA原理

    不同点
    1.Mybatis 优势和缺点

    • 可以更加精确地定位 SQL 语句,进行 SQL 优化
    • 映射条件灵活,可以根据不同条件组装 SQL
    • 使用 Mapper 的接口编程,只要一个接口和一个 XML 文件就可以创建映射,简化开发过程
    • 面向 SQL 语句,数据库移植性差

    2.Hibernate 优势和缺点

    • 完全面向对象,数据库无关性好,O/R映射能力强
    • 对对象的维护和缓存要比 Mybatis 好,对增删改查的对象的维护更方便
    • 数据库移植性好
    • 有更好的二级缓存机制,可以使用第三方缓存。Mybatis 本身提供的缓存机制不佳

    总结:

    • Mybatis可以进行更细致的SQL优化,查询必要的字段,但是需要维护SQL和查询结果集的映射。数据库的移植性较差,针对不同的数据库编写不同的SQL。

    • Hibernate对数据库提供了较为完整的封装,封装了基本的DAO层操作,有较好的数据库移植性。但是学习周期长,开发难度大于Mybatis。

    Hibernate 延迟加载机制

    • 在使用hibernate的session.load(),query.iterate()等方法时,Hibernate返回的是一个空对象(除主键属性外都是null),并没有去查询数据库;而是在使用返回对象的时候才会去查询数据库,并将查询结果注入到该对象中;这种查询时机推迟到对象访问的机制称之为延迟加载。

    • 延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。

    • 在Hibernate中提供了对实体对象的延迟加载以及对集合的延迟加载,另外在Hibernate3中还提供了对属性的延迟加载。

    参考文章

    HashMap底层为什么使用红黑树

    需要注意:链表长度大于 8 个才转换红黑树
    原因:
    链表的时间复杂度是O(n),红黑树的时间复杂度O(logn)
    因为红黑树需要进行左旋,右旋操作, 而单链表不需要,
    以下都是单链表与红黑树结构对比。
    如果元素小于8个,查询成本高,新增成本低
    如果元素大于8个,查询成本低,新增成本高

    【可以参考另外一篇笔记红黑树原理相关
    个人认为红黑树维护性更高,每次插入、删除的平均旋转次数远小于平衡树(最多只需要旋转三次

    ArrayList为什么扩容为原来的1.5倍

    具体来说,应该是往 ArrayList 添加第 11 个元素时会扩容 1.5 倍

    参考文章
    在这里插入图片描述

    添加一个元素,首先计算当前的list所需最小的容量大小,是否需要扩容等。当需要扩容时:

    1.得到当前的ArrayList的容量(oldCapacity)。

    2.计算除扩容后的新容量(newCapacity),其值(oldCapacity + (oldCapacity >> 1))约是oldCapacity 的1.5倍。

    这里采用的是移位运算(关于移位运算,后续会讲到)。为什么采用这种方法呢?应该是出于效率的考虑。

    3.当newCapacity小于所需最小容量,那么将所需最小容量赋值给newCapacity。

    4.newCapacity大于ArrayList的所允许的最大容量,处理。

    5.进行数据的复制,完成向ArrayList实例添加元素操作。

    由上述可知,所说的1.5倍扩容,并不确切。最后真正的容量,可能不止1.5倍,也许还要大。

    什么是 CAS (了解)

    Compare and Swap,即比较再交换。

    简概:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。

    CAS算法原理

    PageHelper 原理

    PageHelper详细说明

    聚簇索引和非聚簇索引区别

    • 聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。
    • 在聚簇索引之上创建的索引称之为辅助索引(非聚簇索引),辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的Page Directory找到数据行。

    聚簇索引和非聚簇索引的区别

    聚簇索引的叶子节点存放的是主键值和数据行,支持覆盖索引;二级索引的叶子节点存放的是主键值或指向数据行的指针。

    由于节子节点(数据页)只能按照一颗B+树排序,故一张表只能有一个聚簇索引。辅助索引的存在不影响聚簇索引中数据的组织,所以一张表可以有多个辅助索引

    参考文章

    Redis 为什么那么“快”

    • 纯内存KV操作
    • 内部是单线程实现的(不需要创建/销毁线程,避免上下文切换,无并发资源竞争的问题)
    • 异步非阻塞的I/O(多路复用)

    Redis使用单线程,相比于多线程快在哪里?
    Redis的瓶颈不在线程,不在获取CPU的资源,所以如果使用多线程就会带来多余的资源占用。比如上下文切换、资源竞争、锁的操作。

    • 上下文的切换
      上下文其实不难理解,它就是CPU寄存器和程序计数器。主要的作用就是存放没有被分配到资源的线程,多线程操作的时候,不是每一个线程都能够直接获取到CPU资源的,我们之所以能够看到我们电脑上能够运行很多的程序,是应为多线程的执行和CPU不断对多线程的切换。但是总有线程获取到资源,也总有线程需要等待获取资源,这个时候,等待获取资源的线程就需要被挂起,也就是我们的寄存。这个时候我们的上下文就产生了,当我们的上下文再次被唤起,得到资源的时候,就是我们上下文的切换。
    • 竞争资源
      竞争资源相对来说比较好理解,CPU对上下文的切换其实就是一种资源分批,但是在切换之前,到底切换到哪一个上下文,就是资源竞争的开始。在我redis中由于是单线程的,所以所有的操作都不会涉及到资源的竞争。
    • 锁的消耗
      对于多线程的情况来讲,不能回避的就是锁的问题。如果说多线程操作出现并发,有可能导致数据不一致,或者操作达不到预期的效果。这个时候我们就需要锁来解决这些问题。当我们的线程很多的时候,就需要不断的加锁,释放锁,该操作就会消耗掉我们很多的时间

    I/O复用,非阻塞模型
    对于I/O阻塞可能有很多人不知道,I/O操作的阻塞到底是怎么引起的,Redis又是怎么解决的呢?

    • I/O操作的阻塞:当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。

    • Redis采用多路复用:I/O 多路复用其实是在单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流。select, poll, epoll 都是I/O多路复用的具体的实现。epoll性能比其他几者要好。redis中的I/O多路复用的所有功能通过包装常见的select、epoll、evport和kqueue这些I/O多路复用函数库来实现的。

    • select(),poll()缺点:

      1. 涉及到用户态跟内核态的切换,耗性能,需要上下文切换,消耗资源大;
      2. 返回值是 int,只能知道就绪的socket个数,需要重新轮询找出是具体哪个socket就绪。
      • select()跟poll()主要区别:传参不一样——select():bitmap;poll():数组
        解决了bitmap长度 1024 难以修改的问题
    • epoll():

    教学视频
    参考文章1
    参考文章2

    Hash表处理冲突的方式

    开放地址法

    • 线性探测法:若当前想放的位置有值了,就往后寻找最近一个空位置,将值放入。
      可能会造成某一块区域数据很密集(如 上面代码中,18岁的学生有很多个,从“18”该放的位置起数据很密集),不推荐。
    • 二次探测法:若当前想放的位置有值了,就往后查找 i^2 个位置是否为空,否则就继续指导找到空的位置(如:当前想放在位置 1,位置不为空,往后移动 1 ^ 2 位置查看是否为空;结果位置 2 有值,再往后移动 2 ^ 2 个位置,结果2 + 2 ^ 2 = 6 即位置6 为空,放下数值。否则继续前面操作)。
      可能会造成很多位置的浪费,即数组长度过长中间却很多空位置。
    • 再哈希法:需要两个(或三个及以上)散列函数。假设用函数一发现当前位置不为空,不继续调用函数一寻找空位置,而是转调用函数二寻找空位置

    链地址法
    在这里插入图片描述

    死锁的四个必要条件

    以下条件缺一不可

    • 互斥条件:一个锁同一时刻只能被一个线程所获取
    • 请求与保持条件:既想请求获取新的锁,又不愿意释放原有的锁
    • 不剥夺条件:当想获取某把锁,而在等待某线程释放该锁时,不能去剥夺此时拥有该锁的线程的持有权
    • 循环等待条件:一直等待直到想获取的锁被其他线程释放

    JVM 内存模型

    在这里插入图片描述
    JVM内存主要分为:程序计数器,Java虚拟机栈,本地方法栈,Java堆,方法区。

    • 程序计数器: 为了线程切换能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。计数器记录的是正在执行的虚拟机字节码指令的地址。

    • Java虚拟机栈: 每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接(例如多态就要动态链接以确定引用的状态)、方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址)。
        其中 64 位长度的 long 和 double 类型的数据会占用 2 个局部变量空间(Slot),其余的数据类型只占用 1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

    • 本地方法栈: Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。

    • Java 堆: Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。

    • 方法区: 方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量(final )、静态变量(static)、即时编译器编译后的代码等数据。

      一个类中主要有:常量、成员变量、静态变量、局部变量。其中常量与静态变量位于方法区,成员变量位于 Java 堆,局部变量位于 Java 虚拟机栈。
        
        运行时常量池: 是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
        
      Java内存模型:每个线程都有一个工作内存,线程只可以修改自己工作内存中的数据,然后再同步回主内存,主内存由多个线程共享。

    参考文章

    JAVA 内存模型

    Java Memory Model (JAVA 内存模型,JMM)描述线程之间如何通过内存(memory)来进行交互。具体说来,JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory,实际上是一个虚拟的概念),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问的,变量在程序中的传递,是依赖主存来完成的。具体的如下图所示:
    在这里插入图片描述
    JMM描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存中读取出变量这样的底层细节。
    所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中变量的一份拷贝)。

    JMM的两条规定

    1、线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;
      
    2、不同的线程之间无法直接访问其他线程工作内存中的变量,线程变量值的传递需要通过主内存来完成。

    追加问题

    1.描述一下jvm内存模型

    jvm内存模型分为5个区域,其中线程独占的区域是栈、本地方法栈、程序计数器,线程共享的区域是堆、方法区。

    2.描述一下java内存模型

    回答这个问题一定要问清楚是不是要问java内存模型,确认是的话,可以回答:java内存模型规定了变量的访问规则,保证操作的原子性、可见行、有序性。

    3.谈一下你对常量池的理解

    常量池是类在编译后储存常量的一块区域,当程序运行到该类,常量池的大部分数据会进入运行时常量池,字符串会进入字符串常量池。

    4.什么情况下会发生栈内存溢出?和内存溢出有什么不同?

    栈内存溢出指程序调用的栈深度多大或栈空间不足时抛出的StackOverflowError。

    一般所谓内存溢出指的是堆内存溢出,抛出的是OutOfMemoryError:java heap space。

    在jdk1.7中,还可能出现永久代内存溢出,抛出的是OutOfMemoryError: PermGen space

    在jdk1.8中,则会出现元空间溢出,抛出的是OutOfMemoryError:MetaSpace

    5.String str = new String(“abc”)创建了多少个实例?

    虽然很多博客都告诉我们创建了两个对象:一个字符串abc对象和一个字符串常量池里指向abc的引用对象。

    但实际情况要更复杂一点。

    实际上在执行完String str = new String(“abc”)之后,其实只创建了一个对象:堆里的字符串对象。而str直接指向该对象。在执行intern()方法后,才会到字符串常量池创建引用对象。当然有时候这个过程会自动完成,但情况比较复杂,难以确定。

    有很多面试官其实自己也搞不清,所以不妨先告诉他创建了两个对象,然后再分析一番,效果更好。

    参考文章

    JVM 内存模型中的 栈帧

    • 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
    • 每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。
    • 一个线程中的方法调用链可能会很长,很多方法都同时处理执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)

    Session 怎样实现

    其实session是一个存在服务器上的类似于一个散列表格的文件。里面存有我们需要的信息,在我们需要用的时候可以从里面取出来。类似于一个大号的map吧,里面的键存储的是用户的sessionid,用户向服务器发送请求的时候会带上这个sessionid。这时就可以从中取出对应的值了。

    其他参考文章

    阻塞队列底层怎么实现的(待)

    B+树索引和 Hash 索引的区别

    B+树索引:具有动态平衡的特点。
    1.支持范围查询。
    2.等长的访问路径,访问时间根据数据量的变化相对稳定。
    3.有明确的查找方向。

    Hash索引:具有查找速度快的特点。
    1.仅仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询。
    2.无法被用来避免数据的排序操作。
    3.不能利用部分索引键查询。
    4.在任何时候都不能避免表扫描。
    5.遇到大量Hash值相等的情况后性能降低。

    总结:
    数据量大选择B+树索引,数据量少选择Hash索引

    ThreadLocal

    • ThreadLocal从字面意思来理解,是一个线程本地变量,也可以叫线程本地变量存储。有时候一个对象的变量会被多个线程所访问,这个时候就会有线程安全问题,当然可以使用synchronized关键字来为该变量加锁,进行同步处理来限制只能有一个线程来使用该变量,但是这样会影响程序执行的效率,这时ThreadLocal就派上了用场;
    • 使用ThreadLocal维护变量的时候,会为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个当前变量。这样同时有多个线程访问该变量并不会相互影响,因为他们都是使用各自线程存储的变量,所以不会存在线程安全的问题。
    • 同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式,前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问且互不影响。

    ThreadLocal实现原理:

    • 每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。也就是说ThreadLocal本身不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。值得注意的是图中(图片摘自网络)的虚线,表示ThreadLocalMap是使用ThreadLocal的弱引用作为key的,弱引用的对象在GC时会被回收。

    参考文章

    ThreadLocal 的内存泄露解决

    参考文章1
    参考文章2

    Spring 事务隔离级别

    参考文章

    一些简答题

    一些总结的较好的文章

    • java中wait和sleep有什么区别?多线程条件下如何保证数据安全?

    答:最大区别是等待时wait会释放锁,而sleep会一直持有锁,wait通常用于线程时交,互,sleep通常被用于暂停执行。

    • java中volatile和synchronized有什么区别?

    1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
    2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
    3.volatile仅能实现变量的修改可见性,并不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
    4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
    5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

    • 有了解java的原子类?实现原理是什么?

    答:采用硬件提供原子操作指令实现的,即CAS。每次调用都会先判断预期的值是否符合,才进行写操作,保证数据安全。

    • spring主要使用了哪些?IOC实现原理是什么?AOP实现原理是什么?

    答:spring主要功能有IOC,AOP,MVC等,IOC实现原理:先反射生成实例,然后调用时主动注入。AOP原理:主要使用java动态代理,

    • mybatis有了解吗?它与hibernate有什么区别?项目中,你会选哪个?

    答:两者都是轻量级ORM框架,hibernate实现功能比较多,通过HQL操作数据库,比较简单方便,但hibernate自动生成的sql相长,不利测试和查找原因。复杂sql时,编写比较困难,同时性能也会降低。mybatis是半自动化,手动编写SQL语句,同时提供丰富的参数判断功能。sql语句较清晰,可以直接进行测试,性能也较好,操作起来非常简单。同时hibernate容易产生n+1问题。hibernate学习成本较mybatis高。国内一些大公司基本上使用mybatis

    • 说说数据库性能优化有哪些方法?

    答:使用explain进行优化,查看sql是否充分使用索引。避免使用in,用exist替代,字段值尽可能使用更小的值,任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。使用连接查询(join)代替子查询。

    在表的多列字段上建立一个索引,但只有在查询这些字段的第一个字段时,索引才会被使用。

    • HTTP请求方法get和post有什么区别?

    1:Post传输数据时,不需要在URL中显示出来,而Get方法要在URL中显示。

    2:Post传输的数据量大,可以达到2M,而Get方法由于受到URL长度限制,只能 传递大约1024字节.

    3:Post就是为了将数据传送到服务器段,Get就是为了从服务器段取得数据.而Get 之所以也能传送数据,只是用来设计告诉服务器,你到底需要什么样的数据.Post 的信息作为http请求的内容,而Get是在Http头部传输的。

    • linux命令熟悉?查看某个线程命令是什么?查看整个机器负载命令?文件内容快速查找命令是什么?

    查看线程:ps -ef|greptomcat

    查看负载:top

    文件内容查找:vi /aa test.txt 或者先打开文件,再查找: vi test.txt /aa

    • JVM内存模型是如何?垃圾回收机制有哪些?如何对JVM进行调优?

    答:由栈和堆组成,栈是运行时单位,堆内存则分为年轻代、年老代、持久代等,年轻代中的对象经过几次的回收,仍然存在则被移到年老代;持久代主要是保存class,method,filed等对象。

    sun回收机制:主要对年轻代和年老代中的存活对象进行回收,分为以下:

    年轻代串行(Serial Copying)、年轻代并行(ParNew)、年老代串行(SerialMSC),年老代并行(Parallel Mark Sweep),年老代并发(Concurrent Mark-Sweep GC,即CMS)等等,目前CMS回收算法使用最广泛。

    JVM调优主要是对堆内容和回收算法进行配置,需要对jdk产生的回收日志进行观察,同时通过工具(Jconsole,jProfile,VisualVM)对堆内存不断分析,这些优化是一个过程,需要不断地进行观察和维护。

    • java抽象类和接口有什么区别?项目中怎么去使用它们?

    相同点:

    A. 两者都是抽象类,都不能实例化。

    B. interface实现类及abstractclass的子类都必须要实现已经声明的抽象方法。

    不同点:

    A. interface需要实现,要用implements,而abstractclass需要继承,要用extends。

    B. 一个类可以实现多个interface,但一个类只能继承一个abstractclass。

    C. interface强调特定功能的实现,而abstractclass强调所属关系。

    D. 尽管interface实现类及abstrctclass的子类都必须要实现相应的抽象方法,但实现的 形式不同。interface中的每一个方法都是抽象方法,都只是声明的 (declaration, 没有方 法体),实现类必须要实现。而abstractclass的子类可以有选择地实现。

    使用:

    abstract:在既需要统一的接口,又需要实例变量或缺省的方法的情况下,使用abstract;

    interface:使用: 类与类之前需要特定的接口进行协调,而不在乎其如何实现。 作为能 够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。需要将一组类 视为单一的类,而调用者只通过接口来与这组类发生联系。需要实现特定的多项功能, 而这些功能之间可能完全没有任何联系。

    展开全文
  • java面试3

    2020-04-23 04:08:09
    京东一面 简单介绍下自己 做过什么项目 学过什么专业课/技术 ...可重入锁的设计思路什么 乐观锁和悲观锁 synchronized机制 hashmap原理,处理哈希冲突用的哪种方法(拉链) 还知道什么处理哈希冲突的方...
  • 乐观锁、悲观锁 读写分离怎么实现 springmvc的事务处理 主从数据库的配置 切面什么?(AOP??待查) autowired和resource的区别 mysql的引擎有几种?区别什么 hibernate的一级缓存、二级缓存指的什么?应...
  • 悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过记录加 version 来获取资源,性能较悲观锁有很大的提高。 ...
  • java多线程总结

    2019-03-09 11:55:29
    文章目录多线程线程池满了如何处理额外的请求同一个对象的两个同步方法能否被两个线程同时调用为什么要用线程池悲观锁和乐观锁的区别,怎么实现什么线程死锁,死锁如何产生,如何避免线程死锁线程池用过吗,都有...
  • java实用资料

    2018-07-13 10:52:00
    线程是怎么处理二个以上的对象同时处理一个全局变量 3.读文件为啥不用字符流 4.请求鉴定,各种错误码502-500-401-404 403 302 304 5.数据库锁,共享所,排他锁,乐观锁,悲观锁...
  • 1.java中有哪些集合?哪些有序的,哪些无序的。 1.java中有哪些集合?哪些有序的,哪些无序的。 2.怎么实现多线程?一个线程的生命周期多久。 3.Sql如何优化?...8.乐观锁和悲观锁 sadasdsadsa...
  • java入坑计划

    2017-05-17 17:10:00
    数据库用好,关注事物的设置是否合理,是否产生脏数据,是否要用悲观锁,乐观锁、、、、、  ③.懂什么集群、分布式,怎么向别的系统发送请求,交换数据,webservice、、、、、  ④.会用Java...
  • 可重入锁的设计思路什么 乐观锁和悲观锁 synchronized机制 hashmap原理,处理哈希冲突用的哪种方法(拉链) 还知道什么处理哈希冲突的方法(开放地址检测) 开放地址检测怎么实现的 从哈希表中删除一个元素...
  • 1.怎么实现hash表,解决冲突的方法? 2.你对二叉搜索树的理解,对B-树的理解? 3.求两个等长有序数组的中位数 ...悲观锁和乐观锁的区别? 7.JVM的内存模型? 8.什么Servlet?有哪些方法? 9.怎么理解http tcp/
  • 176.说一下乐观锁和悲观锁? 177.mysql 问题排查都有哪些手段? 178.如何做 mysql 的性能优化? 十八、Redis 179.redis 什么?都有哪些使用场景? 180.redis 有哪些功能? 181.redis 和 memecache 有什么区别? ...
  • 可重入锁的设计思路什么 乐观锁和悲观锁 synchronized机制 hashmap原理,处理哈希冲突用的哪种方法(拉链) 还知道什么处理哈希冲突的方法(开放地址检测) 开放地址检测怎么实现的 从哈希表中删除一个元素,再...
  • 2020java面试(三)

    2020-03-27 19:00:08
    一面 被HR带领到工作区前的面试区域,有几对正在进行着面试,很热闹,看来最近美团需求量不小。...乐观锁和悲观锁,应用场景有哪些?什么情况下会发生死锁,怎么处理死锁? hashMap的原理,由此延伸问红黑树...
  • 【数据库】乐观锁与悲观锁的区别 107 【数据库】数据库的三范式 107 【数据库】inner/left/right/full join的区别 109 【数据库】哪些字段该添加索引,哪些不添加? 109 【数据库】分页查询语句怎么写? 110 Mysql...
  • 后端面经

    2017-06-16 17:50:59
    连接断了怎么处理? 引用和指针的区别 排序,复杂度 熟不熟悉Linux下编程 2020.3.26——字节跳动 C++后端 一面 悲观锁&乐观锁 数据库中事务 (你刚刚提到“索引”)解释一下索引...
  • 《网络是怎么连接的》 这本书是一本日本作者写的。文章围绕着在浏览器中输入网址开始,一路追踪了到显示出网页内容为止的整个过程,图文并茂生动有趣,非常推荐! 《图解 HTTP》 也是一名日本作者写的。这本书对 ...
  • 你知道程序是怎么处理时区问题的么? 聊一聊理财 人物 罗永浩 面试真题&面经分享 美团面经 来自朋友最近阿里、腾讯、美团等P7岗位面试题 淘宝|蚂蚁|菜鸟|盒马|嘀嘀|饿了么面经(已拿多个offer) 苦修月余...
  • 4.1.6 JAVA8的ConcurrentHashMap为什么放弃了分段,有什么问题吗,如果你来设计,你如何设计。 4.1.7 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的。 4.1.8 抽象类和接口的区别,类可以继承多个类么...
  •  本书一本关于Oracle 9i & 10g数据库体系结构的权威图书,涵盖了所有最重要的Oracle体系结构特性,包括文件、内存结构和进程,和闩,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并利用具体的...
  •  本书一本关于oracle database 9i、10g 和11g 数据库体系结构的权威图书,涵盖了所有重要的oracle 体系结构特性,包括文件、内存结构和进程,和闩,事务、并发和多版本,表和索引,数据类型,分区和并行,以及...
  • 6.1 什么是锁? 181 6.2 锁定问题 184 6.2.1 丢失更新 184 6.2.2 悲观锁定 185 6.2.3 乐观锁定 187 6.2.4 乐观锁定还是悲观锁定? 197 6.2.5 阻塞 198 6.2.6 死锁 201 6.2.7 升级 206 6.3 类型 206 ...
  • 第05节、悲观锁与乐观锁区别 第06节、重入锁 第07节、读写锁 第08节、CAS无锁机制 第09节、自旋锁 资料+源码.rar 0006-蚂蚁课堂(每特学院)-2期-数据交换格式&反射机制&SpringIOC;原理分析 第01节、什么数据交换...
  • 1.3.5 “怎么能让应用运行得更快?”.................................... 86 1.3.6 DBA 与开发人员的关系........................................... 90 1.4 小结.................................................
  • Oracle编程艺术

    热门讨论 2010-07-19 11:51:57
    1.3.5 “怎么能让应用运行得更快?”................................................114 1.3.6 DBA与开发人员的关系.........................................................119 1.4 小结........................
  • 可重入锁的设计思路什么 乐观锁和悲观锁 synchronized机制 hashmap原理,处理哈希冲突用的哪种方法(拉链) 还知道什么处理哈希冲突的方法(开放地址检测) 开放地址检测怎么实现的 从哈希表中删除一个元素...
  • 可重入锁的设计思路什么 乐观锁和悲观锁 synchronized机制 hashmap原理,处理哈希冲突用的哪种方法(拉链) 还知道什么处理哈希冲突的方法(开放地址检测) 开放地址检测怎么实现的 从哈希表中删除一个元素...
  • 一点前言 其实对于大厂面试,我最后想要强调的一点就是心态真的很重要...4 数据库的悲观锁和乐观锁 5 数据的索引有什么用,怎么实现 6 联合索引的匹配原则 7 数据库万级变成亿级,怎么处理。分库分表,分片规则hash和取

空空如也

空空如也

1 2
收藏数 33
精华内容 13
关键字:

java是怎么处理悲观锁的

java 订阅