我是如何进入阿里巴巴的-面向春招应届生Java面试指南(七)

网络基础

HTTP TCP 对象的内存布局

对象头(header)、实例数据(Instance Data)、对齐填充

java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位(包括锁标志位和是否是偏向锁)

锁一共4种状态,级别从低到高分依次是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态

jvm垃圾收集

1、内存划分:栈内存,堆内存,方法区

2、堆内存划分

新生代,老年代和永久代

新生代又可划分为Eden区,Survivor 1区和Survivor 2区。新创建的对象会分配在Eden区,在经历一次Minor GC后会被移到Survivor 1区,再经历一次Minor GC后会被移到Survivor 2区,直到升至老年代,需要注意的是,一些大对象(长字符串或数组)可能会直接存放到老年代

大多数情况下,对象在新生代Eden区中分配,当Eden没有足够空间进行分配时,虚拟机将发起一次Minor GC(发生在新生代的垃圾收集动作)

新的对象实例会优先分配在新生代,在经历几次Minor GC后(默认15次),还存活的会被移至老年代

Full GC:指发生在老年代的GC

Minor GC发生在新生代,当Eden区没有足够空间时,会发起一次Minor GC,将Eden区中的存活对象移至Survivor区。Major GC发生在老年代,当升到老年代的对象大于老年代剩余空间时会发生Major GC

公众号推荐

  • 全网唯一一个从0开始帮助Java开发者转做大数据领域的公众号~

  • 大数据技术与架构或者搜索import_bigdata关注~

  • 海量【java和大数据的面试题+视频资料】整理在公众号,关注后可以下载~

表的优化

1、定长与变长分离

定长:int,char,time

变长:varchar,text,blob

2、常用字段和不常用字段分离

3、一对多,需要关联统计的字段上,添加冗余字段(论坛版块列表,回复数)

4、列选择原则:

1)字段类型优先级:整型>date,time>char,enum>varchar>blob.text

2)够用就行,不用慷慨

3)尽量避免使用NULL():在磁盘上占用更大的空间,NULL()不利于索引,要用特殊的字符来表示

5、MYSQL索引类型:BTree索引、Hash索引

hash索引是在memory表里的,即在内存中存在

hash索引的弊端:

1)无法对范围查询进行优化

2)无法利用前缀索引

3)无法对排序进行优化

4)必须回行,即通过索引拿到表的位置,必须回到表中取数据

6、BTree索引常见误区

1)在where条件常用的列上都加上索引——独立索引只能用上一个

2)在多列上建立联合索引后,查询哪个列,索引都能发挥作用——必须依照索引的顺序,且满足左前缀要求

索引可以提高查询速度,排序速度和分组统计的速度

7、创建联合索引

复制代码 create table t4 ( c1 tinyint(1) not null default 0, c2 tinyint(1) not null default 0, c3 tinyint(1) not null default 0, c4 tinyint(1) not null default 0, c5 tinyint(1) not null default 0, index c1234(c1,c2,c3,c4) ); 复制代码 8、分析索引的使用情况:explain

Innodb和myisam都是使用的BTree索引

但是myisam使用的是非聚簇索引——索引表和数据表分离,其索引指向某行在磁盘上的位置,不同索引树之间并无关联

innodb使用的是聚簇索引,数据和索引一块存放,不用回行,其索引,指向对主键的引用,非主键索引树指向主键索引

叶子比较重,且主键无规律,容易出现叶分裂

索引覆盖,优化的目的在于尽量少从磁盘上拿数据 性能分析工具:show profiles—— set profiling=1

9、理想的索引:

1)查询频繁

2)区分度高

3)长度小

4)尽量能覆盖常用查询字段

index(cat_id,price),index(cat_id,brand_id,shop_price);

10、索引与排序

排序可能发生两种情况:

1)对于索引覆盖,直接在索引上查询时,就是有顺序的,using index

2)如果没有索引,先取出临时数据,形成临时表做firesort(文件排序,可能在磁盘上,也可能在内存中)

我们的争取目标——取出来的数据本身就是有序的,利用索引来排序

对于myisam而言,对所有数据排序时并不是每取一次索引然后去磁盘上取相应数据,而是将所有数据都取出来进行filesort

独立的索引在每次查询中只能使用一个,应该尽量避免查询中出现filesort

where 后面的字段和 order by后面的字段不一致的时候,也会出新filesort现象,因为独立索引只能使用一个,查询时用到了一个索引,在排序时就不能使用排序字段的索引了

11、索引相关的操作语句

冗余索引较为常见,常用的为两个相同字段按先后顺序分别建立不同的索引

12、表修复——索引碎片及维护

方式一:nop操作,不对数据产生实际影响——alter table xxx engine InnoDB

方式二:optimize table name

注意: innodb来说,

1: 主键索引 既存储索引值,又在叶子中存储行的数据

2: 如果没有主键, 则会Unique key做主键

3: 如果没有unique,则系统生成一个内部的rowid做主键.

4: 像innodb中,主键的索引结构中,既存储了主键值,又存储了行数据,这种结构称为”聚簇索引”

13、SQL语句优化

sql语句的执行时间花在哪了?

查找——沿着索引查找,慢者可能全表扫描

取出——查到行后,把数据取出来

如何查询快?

1)查询——联合索引的顺序,区分度,长度

2)取的快,索引覆盖

3)传输更少,更少的行和列

尽量走索引进行查询

14、explain 解释一个查询语句的执行计划

字段详解:

   id:查询的序号,表明select语句
复制代码

  select_type:

      simple:不含子查询

      primary:含子查询

        dependent subquery:非from子查询

        derived:from子查询

        union 和 union result

  table:查询的表名

    实际的表名,表的别名,derived(from类型的子查询),null (union)

  type:索引所发挥的作用

    all:做全表扫描,where后面查询条件列中没有索引。利用索引来排序,但是取出来是所有的节点,所以也会出现all的情况

    Index:比all性能稍微好点,all扫描所有的数据行,相当于data_all,indux扫描所有的索引节点,相当于index_all

    range:索引范围扫描

    ref:精准查询,可以直接引用到某些行数据

    eq_ref:

    const,system,null:查询优化到常量级别,甚至不需要查询时间,一般按主键查询时,能出现这种情况

  possible_keys:可能用到的键

  key:真正用到的键

  key_len:表的长度

  ref:两表联查时候的引用关系

  rows:本次查询返回的行数

  extra:额外的信息

    index:用到了索引覆盖,效率非常高

    using where:光靠索引定位不了,还得where进行判断

    using temporary:是指用上了临时表,order by与group by不同列时,或group by,order by别的表的列

    using filesort:文件排序(文件可能在磁盘,也可能在内存)

  

  如果取出的列含有text,或者更大的如mediumtext等,filesort将会发生在磁盘上

  show status like '%_table%':查看磁盘使用情况

15、In型子查询的误区:

mysql的查询优化器,针对In型做了优化,被改成了exists子查询的执行效果,当goods表越大时,查询速度越慢

改进:用连接查询代替子查询

explain select goods_id,g.cat_id,g.goods_name from goods as g

inner join (select cat_id from ecs_category where parent_id=6) as t

using(cat_id) \G

16、from型子查询

注意::内层from语句查到的临时表, 是没有索引的.

所以: from的返回内容要尽量少.

17、count(*)优化

18、group by、order by

19、union总是要产生临时表

尽量使用union all,不去重,不排序

20、limit 及翻页优化

limit offset,N, 当offset非常大时, 效率极低,

原因是mysql并不是跳过offset行,然后单取N行,

而是取offset+N行,返回放弃前offset行,返回N行.

效率较低,当offset越大时,效率越低

show profiles;

set profiling=1;

show profile for query 5

尽量沿着索引爬行

select id,name from lx_com where id>5000000 limit 10;

非要物理删除,还要用offset精确查询,还不限制用户分页,怎么办?

select id,name from lx_com inner join (select id from lx_com limit 5000000,10) as tmp using(id);

数据库底层原理,调优

数据表设计原则

1、从空间上考虑用varchar,从效率上考虑,用char

varchar类型的实际长度,是它的值的实际长度+1,+1的字节用于保存实际用了多大长度

2、存储过程

可移植性较差

定义存储过程:

delimiter %

create procedure 过程名(参数1,参数2)

begin

  sql语句

end

调用方式:

call 过程名(参数1,参数2)

存储过程参数传递;

IN参数

in传入参数,把参数传递到过程内部

特点:读取外部变量值,且有效范围仅限存储过程内部

out:传出参数

inout:传入传出参数

into:赋值

mysql中的变量

1)局部变量。

局部变量一般用在sql语句块中,比如存储过程的begin/end。其作用域仅限于该语句块,在该语句块执行完毕后,局部变量就消失了。

局部变量一般用declare来声明,可以使用default来说明默认值。

drop procedure if exists add;

create procedure add( in a int, in b int)

begin

declare c int default 0;

set c = a + b;

select c as c;
复制代码

end;

2)用户变量

用户变量的作用域要比局部变量要广。用户变量可以作用于当前整个连接,但是当当前连接断开后,其所定义的用户变量都会消失。

用户变量使用如下(这里我们无须使用declare关键字进行定义,可以直接这样使用):

select @变量名

对用户变量赋值有两种方式,一种是直接用"="号,另一种是用":="号。其区别在于使用set命令对用户变量进行赋值时,两种方式都可以使用;当使用select语句对用户变量进行赋值时,只能使用":="方式,因为在select语句中,"="号被看作是比较操作符。

3)会话变量

4)全局变量

触发器

与数据表有关,当表出现变化的时候(增、删、改),自动执行其他的特定的操作

触发器的格式

语法:create trigger 触发器名称 触发的时间 触发的动作

on 表名 for each row 触发器状态

触发器状态:随便起名字

触发的时机:before/after 在执行动作之前还是之后

触发的动作:

指的激发触发程序的语句类型:insert,update,delete

触发器创建语法四要素:

1)监视地点(table)

2)监视事件(insert/update/delete)

3)触发时间(after/before)

4)触发事件(insert/update/delete)

查看触发器:show triggers \G

查看触发器创建过程:show create trigger triggername \G

事务:(只有InnoDB引擎支持)

开启事务:START TRANSACTION

提交当前事务:COMMIT

关闭自动提交:set autocommit=0;

回滚:rollback

MySQL相关的配置文件:

主配置文件:/etc/my.cnf

进程通讯文件:/var/lib/mysql/mysql.sock

日志文件:/var/log/mysqld.log

进程ID文件:/var/run/mysqld/mysqld.pid

二进制文件:在my.cnf中增加

log-bin=mysql-bin.log

慢查询日志文件:在my.cnf中增加

log-slow-queries

存储引擎层:存储和提取数据以及事务处理

MyISAM的特性:

1)不支持事务,宕机时会破坏表

2)使用较小的内存和磁盘空间

3)基于表的锁,表级锁(加锁与并发)表级锁定影响性能

4)只缓存index,数据由os缓存

5)不支持外键约束,但是支持全文索引。

MyISAM调优精要:

1、设置合适的索引(缓存机制)

2、调整读写优先级,根据实际需求确保重要操作更优先执行

3、启用延迟插入,改善大批量写入性能

4、尽量顺序操作,让insert数据都写入到尾部,减少阻塞

5、降低并发数,减少对数据库的访问,使用消息队列

6、对于相对静态的数据库数据,充分利用Query Cache或者memcached缓存服务可以极大提高访问速率

7、

InnoDB存储引擎的特性

1)支持事务四个级别ACID

2)支持行锁(行级锁定)

3)聚簇索引

4)支持外键

5)支持崩溃数据自修复

6)具有高效的缓存特性,能缓存索引,也能缓存数据。

默认使用InnoDB引擎配置

default-storage-enigine=innodb

类加载器及其加载过程

Hibernate 机制

MYSQL

ORACLE

多线程技术,各种锁机制

1、moniterenter和moniterexit 指令对应于synchronized,更深一步是lock和unlock

2、执行moniterenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前已经拥有了那个对象的锁,把锁的计数器加1,相应的,在执行moniterexit指令时会将计数器减1,当计数器为0时,锁就被释放。

3、和synchronized相比,ReentrantLock增加了一些高级功能,主要有等待可中断,可实现公平锁,以及锁可以绑定多个条件。

4、自旋锁:为了让线程等待,只需让线程执行一个忙循环(自旋),但并不放弃处理器的执行时间,这项技术就是自旋锁

自旋锁可以在时间上和自旋次数上进行控制,自适应自旋锁

5、

大数据相关组件及其原理

设计模式

zookeeper原理

数据库和缓存保持数据的一致性

Concurrent包

虚拟机和调优

缓存一致性问题(如何解决数据库和redis缓存的双写操作)

一致性Hash算法

内存高占用率分析

java内存模型

1、Java内存模型规定了所有变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写准内存中的变量。

2、内存间交互操作

lock、unlock

read、load:两者不能单独出现

use、assign

store、write:两者不能单独出现

3.volatile不保证原子性

特性1:保证此变量对所有线程的可见性

特性2:禁止指令重排序优化

Executor

1.Executor框架的结构

2.ThreadPoolExecutor

ThreadPoolExecutor通常使用工厂类Executors来创建,可以创建的三种类型

SingleThreadExecutor:只有一个线程,适用于需要保证顺序地执行各个任务,并且在任意时间点,不会有多个线程是活动的应用场景

FixedThreadPool:用于需要限制当前线程数量的应用场景,它适用于负载比较重的服务器

CachedThreadPool:是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器

HashMap和ConcurrentHashMap

在并发编程中使用HashMap可能导致程序死循环,而使用线程安全的HashTable效率又非常低下

在多线程中使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空。

ConcurrentHashMap的锁分段技术可有效提升并发访问率

chm由Segment数组结构和HashEntry数组结构组成,Segment是一种可重入锁(ReentrantLock)。

Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素。

定位Segment:

在插入和获取元素的时候,必须先通过散列算法定位到Segment,之所以进行再散列,目的是减少散列冲突,使元素能够均匀的分布在不同的Segment上,从而提高容器的存取效率,通过这种再散列能让数字的每一位都参加到散列运算当中,从而减少散列冲突。

get操作:get操作不需要加锁,因为get方法中要使用的共享变量都定义成立volatile类型,因此可以在多线程中保持可见性,可以被多线程同时读

因为根据Java内存模型的先行发生原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的值。

put操作:判断是否需要对HashEntry数组进行扩容,第二步定位添加元素的位置

先行发生原则

1)程序次序规则

2)管程锁定规则

3)volatile变量规则:对一个volatile变量的写操作先行发生于后面这个变量的读操作

Synchronized

同步代码块使用了 monitorenter 和 monitorexit 指令实现。

同步方法中依靠方法 修饰符上的 ACC_SYNCHRONIZED 实现

锁的四种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态

轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,轻量级锁使用CAS 操作避免了使用互斥量的开销

monitor是线程私有的数据结构,每一个线程都有一个可用的monitor列表,同时还有一个全局可用的列表

每个线程的当前栈帧中都有一个Lock Record锁记录,用于存储锁对象目前的Mark Word的拷贝

虚拟机将使用CAS操作将Mark Word更新为指向Lock Record的指针,如果成功,则拥有轻量级锁

如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁

轻量级锁:利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG)

如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了

CAS(Compare and swap)原理

思想,三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false

CAS是通过unsafe类的compareAndSwap方法实现的

Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据

compareAndSwap:第一个参数是要修改的对象,第二个参数是对象中要修改变量的偏移量,第三个参数是修改之前的值,第四个参数是预想修改后的值

CAS的缺点:ABA问题

如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?

解决办法:java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性

LOCK(AQS)

当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。

而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

REDIS哨兵的主要作用:集群监控和故障转移 )

架构图

qps多少,99线多少 qps 5万 / 500-800ms string stringbuffer stringbuilder区别

string a = "abc" string b = new String("abc"), a == b和 a.equals(b)返回什么值,为什么

hashmap hashtable ConcurrentHashMap区别

hashmap结构,默认长度

ConcurrentHashMap结构,是否和hashmap一样,段锁的实现方式,

数据库隔离级别都有哪个,mysql默认隔离级别是什么,不可重复读和幻读是什么意思

数据库的存储引擎都用过哪些,什么区别,他们的索引什么区别。innodb的主键索引和辅助索引的区别,组合索引,哪种方式走索引

mysql的执行计划都有哪些字段

spring事物传播级别概念,记住概念,实现方式,

spring 有哪些特性,ioc和aop是什么意思,aop使用场景,spring中aop是怎么实现的,cglib和javasist区别

@@@@@@spring中两个对象双向引用是怎么实现的 第一种,解决setter对象的依赖,就是说在A类需要设置B类,B类需要设置C类,C类需要设置A类,这时就出现一个死循环,

spring的解决方案是,初始化A类时把A类的初始化Bean放到缓存中,然后set B类,再把B类的初始化Bean放到缓存中,

然后set C类,初始化C类需要A类和B类的Bean,这时不需要初始化,只需要从缓存中取出即可.

该种仅对single作用的Bean起作用,因为prototype作用的Bean,Spring不对其做缓存

第二种,解决构造器中对其它类的依赖,创建A类需要构造器中初始化B类,创建B类需要构造器中初始化C类,创建C类需要构造器中又要初始化A类,因而形成一个死循环,Spring的解决方案是,把创建中的Bean放入到一个“当前创建Bean池”中,在初始化类的过程中,如果发现Bean类已存在,就抛出一个“BeanCurrentInCreationException”的异常

@@@@给你一个加减乘除的签名 int operator(int a, int b, string oprator),你怎么实现这个功能,策略算法

@@@@设计一个车,可以是小汽车,拖拉机,等车型,它可以行走在公路,泥路,山路上等,你怎么设计。一个设计模式

@@@jvm内存分为几个区,哪写区会有OOM异常,堆包含哪几块。你们用的什么垃圾回收策略, SerialOld 作为CMS失败时候的后备收集器。

说下cms的几个步骤,哪个步骤会STW,CMS的优点缺点,CMS怎么分析数据是否可达,GC ROOT怎么找的,CMS处理失败后怎么继续处理,PALL OLD

老年代怎么分配的空间,(碰撞指针,还是空闲列表)

线程都有什么状态

synchronized怎么实现的,lock怎么实现的,他们的优缺点  1.可重入锁

    如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。

  2.可中断锁

    可中断锁:顾名思义,就是可以相应中断的锁。

    在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

    如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

  3.公平锁和非公平锁

    公平锁以请求锁的顺序来获取锁,非公平锁则是无法保证按照请求的顺序执行。synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

    参数为true时表示公平锁,不传或者false都是为非公平锁。

ReentrantLock lock = new ReentrantLock(true);   4.读写锁

  读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。

  正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

  ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。

  可以通过readLock()获取读锁,通过writeLock()获取写锁。 1.synchronized

  优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛

  缺点:悲观的排他锁,不能进行高级功能

  2.lock

  优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁  

  缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪

  3.相同点 

  都是可重入锁

volatile特性,i++怎么实现的 0: iconst_0 // 生成整数0 1: istore_1 // 将整数0赋值给1号存储单元(即变量i) 2: iload_1 // 将1号存储单元的值加载到数据栈(此时 i=0,栈顶值为0) 3: iinc 1, 1 // 1号存储单元的值+1(此时 i=1) 6: istore_2 // 将数据栈顶的值(0)取出来赋值给2号存储单元(即变量j,此时i=1,j=0) 7: return // 返回时:i=1,j=0 偏向锁,轻量级锁,重量级锁了解吗 自旋锁

互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力。而在很多应用上,共享数据的锁定状态只会持续很短的一段时间。若实体机上有多个处理器,能让两个以上的线程同时并行执行,我们就可以让后面请求锁的那个线程原地自旋(不放弃CPU时间),看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是自旋锁。

如果锁长时间被占用,则浪费处理器资源,因此自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了(默认10次)。

JDK1.6引入自适应的自旋锁:自旋时间不再固定,由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。

锁削除

锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除(主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行)。

锁膨胀

如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。 如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(膨胀)到整个操作序列的外部(由多次加锁编程只加锁一次)。

轻量级锁

轻量级锁并不是用来代替重量级锁(传统锁机制,如互斥等)的,目的是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

HotSpot虚拟机的对象头(Object Header)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32个和64个Bits,官方称它为“Mark Word”,它是实现轻量级锁和偏向锁的关键。另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。

Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下Mark Word的存储内容如下表所示。

存储内容标志位状态 对象Hash值、对象分代年龄 指向锁记录的指针00轻量级锁定 指向重量级锁指针10膨胀(重量级锁定) 偏向线程ID、偏向时间戳、对象分代年龄01可偏向 锁过程

在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word),这时候线程堆栈与对象头的状态如下图所示。

然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个Bits)将转变为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如下图所示。

如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

解锁过程

解锁过程也是通过CAS操作来进行的,如果对象的Mark Word仍然指向着线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

轻量级锁小结

轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

偏向锁

目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。

偏向锁会偏向于第一个获得它的线程(Mark Word中的偏向线程ID信息),如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

假设当前虚拟机启用了偏向锁(启用参数-XX:+UseBiasedLocking,JDK 1.6的默认值),当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。    当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级锁那样执行。偏向锁、轻量级锁的状态转化及对象Mark Word的关系如下图所示。

偏向锁可以提高带有同步但无竞争的程序性能。它同样是一个带有效益权衡(Trade Off)性质的优化,也就是说它并不一定总是对程序运行有利,如果程序中大多数的锁都总是被多个不同的线程访问,那偏向模式就是多余的。

特别说明:尊重作者的劳动成果,转载请注明出处哦~~~http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt364

aqs了解吗,cas实现方式,自旋的缺点,jvm怎么优化自旋(自旋优化这个问题个人觉得应该放在同步快上去问) pinlock不会使线程状态发生切换,mutex在获取不到锁的时候会选择sleep。

  mutex获取锁分为两阶段,第一阶段在用户态采用spinlock锁总线的方式获取一次锁,如果成功立即返回;否则进入第二阶段,调用系统的futex锁去sleep,当锁可用后被唤醒,继续竞争锁。

  Spinlock优点:没有昂贵的系统调用,一直处于用户态,执行速度快。

  Spinlock缺点:一直占用cpu,而且在执行过程中还会锁bus总线,锁总线时其他处理器不能使用总线。

  Mutex优点:不会忙等,得不到锁会sleep。

  Mutex缺点:sleep时会陷入到内核态,需要昂贵的系统调用。    threadlocal实现方式

二叉树遍历方式,哪个遍历方式高,时间复杂度多少,红黑色实现方式,解决啥问题 对二叉树的遍历访问且仅访问所有结点一次,所以时间复杂度为O(n)

Morris算法在遍历的时候避免使用了栈结构,而是让下层到上层有指针,具体是通过底层节点指向NULL的空闲指针返回上层的某个节点,从而完成下层到上层的移动O(1)

手写快速排序,跳表,TOP N问题

双主数据库主键怎么生成的,雪花算法有啥问题

dubbo有什么优点,dubbo默认协议,默认序列化方式

缺省协议,使用基于netty3.2.2+hessian3.2.1交互。

连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO异步传输
序列化:Hessian二进制序列化
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。
适用场景:常规远程服务方法调用
复制代码

zk算法

redis主从同步机制,如果主挂了,怎么在从节点中选择一个节点作为主节点

熔断机制中,线程池隔离和信号隔离的实现方式,设计一个熔断器

mq堆积问题怎么解决

二叉树遍历shell文件排序jvm调优(怎么调) hashmap和hashtable区别聊项目问算法 算法:N * N 数组的的斜杠输出
非算法:主要是线程并发相关
synchronized 的实现原理 lock的实现原理
线程池的调优 newCachedThreadPool() newFixedThreadPool等的区别和参数调优
hashtable hashmap的底层实现机制
spring事务的实现原理
mysql锁机制,索引的类型,主键索引和普通索引的区别 主键一定是唯一性索引,唯一性索引并不一定就是主键 一个表中可以有多个唯一性索引,但只能有一个主键. 主键列不允许空值,而唯一性索引列允许空值.

mysql 多个索引做where条件,执行随机指定一个where有索引,order 有索引,where索引没起作用mysql 建立多个索引,update多个索引操作,有可能索引 dubbo自定义线程池,阻塞队列长度设置 dubbo默认提供了三种线程池,分别是 fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。 (默认,100个) cached 缓存线程池,空闲一分钟自动删除,需要时重建。 limited 可伸缩线程池,但池中的线程数只会增长不会收缩。(为避免收缩时突然来了大流量引起的性能问题)。 dubbo机房,网络异常,全部断网几分钟,后恢复,会有什么问题: 长连接会断开,导致客户端不重连,需要重启 redis mysql同步mysql int类型索引字段a,查询条件, 为字符串会走索引吗,相反条件呢 权限设计 线程池超时时间怎么弄 用submit方法,返回一个Future,然后调用Future中的V get(long timeout, TimeUnit unit)方法,timeout就是超时时间,超时没返回结果就会报异常,捕获异常,然后调用cancel方法

公众号推荐

  • 全网唯一一个从0开始帮助Java开发者转做大数据领域的公众号~

  • 大数据技术与架构或者搜索import_bigdata关注~

  • 海量【java和大数据的面试题+视频资料】整理在公众号,关注后可以下载~

你可能感兴趣的:(java,面试,运维)