Java
中
synchronized
和
ReentrantLock
有什么不同?
相似点:
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如
果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等
待,而进行线程阻塞和唤醒的代价是比较高的
.
区别:
这两种方式最大区别就是对于
Synchronized
来说,它是
java
语言的关键字,是原生语法层面的互
斥,需要
jvm
实现。而
ReentrantLock
它是
JDK 1.5
之后提供的
API
层面的互斥锁,需要
lock()
和
unlock()
方法配合
try/finally
语句块来完成。
Synchronized
进过编译,会在同步块的前后分别形成
monitorenter
和
monitorexit
这个两个字节码
指令。在执行
monitorenter
指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线
程已经拥有了那个对象锁,把锁的计算器加
1
,相应的,在执行
monitorexit
指令时会将锁计算器就
减
1
,当计算器为
0
时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被
另一个线程释放为止。
由于
ReentrantLock
是
java.util.concurrent
包下提供的一套互斥锁,相比
Synchronized
,
ReentrantLock
类提供了一些高级功能,主要有以下
3
项:
1.
等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于
Synchronized
来说可以避免出现死锁的情况。
2.
公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,
Synchronized
锁非公平
锁,
ReentrantLock
默认的构造函数是创建的非公平锁,可以通过参数
true
设为公平锁,但公平锁
表现的性能不是很好。
3.
锁绑定多个条件,一个
ReentrantLock
对象可以同时绑定对个对象。
锁的优化机制了解吗?
从
JDK1.6
版本之后,
synchronized
本身也在不断优化锁的机制,有些情况下他并不会是一个很重量
级的锁了。优化机制包括自适应锁、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁。
锁的状态从低到高依次为
无锁
->
偏向锁
->
轻量级锁
->
重量级锁
,升级的过程就是从低到高,降级在
一定条件也是有可能发生的。
自旋锁
:由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所有没有必要挂起
线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,
可以理解为就是啥也不干,防止从用户态转入内核态,自旋锁可以通过设置
-XX:+UseSpining
来开
启,自旋的默认次数是
10
次,可以使用
-XX:PreBlockSpin
设置。
自适应锁
:自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上
的自旋时间和锁的持有者状态来决定。
锁消除
:锁消除指的是
JVM
检测到一些同步的代码块,完全不存在数据竞争的场景,也就是不需要
加锁,就会进行锁消除。
锁粗化
:锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展到整个操
作序列之外。
偏向锁
:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程
ID
,之后
这个线程再次进入同步块时都不需要
CAS
来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线
程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,当有其他
线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以用过设置
-XX:+UseBiasedLocking
开
启偏向锁。
轻量级锁
:
JVM
的对象的对象头中包含有一些锁的标志位,代码进入同步块的时候,
JVM
将会使用
CAS
方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,
当前线程就尝试自旋来获得锁。
整个锁升级的过程非常复杂,我尽力去除一些无用的环节,简单来描述整个升级的机制。
简单点说,偏向锁就是通过对象头的偏向线程
ID
来对比,甚至都不需要
CAS
了,而轻量级锁主要就
是通过
CAS
修改对象头锁记录和自旋来实现,重量级锁则是除了拥有锁的线程其他全部阻塞。
线程池核心线程数怎么设置呢?
分为
CPU
密集型和
IO
密集型
CPU
这种任务消耗的主要是
CPU
资源,可以将线程数设置为
N
(
CPU
核心数)
+1
,比
CPU
核心数多出
来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦
任务暂停,
CPU
就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用
CPU
的空
闲时间。
IO
密集型
这种任务应用起来,系统会用大部分的时间来处理
I/O
交互,而线程在处理
I/O
的时间段内不会占
用
CPU
来处理,这时就可以将
CPU
交出给其它线程使用。因此在
I/O
密集型任务的应用中,我们
可以多配置一些线程,具体的计算方法是 : 核心线程数
=CPU
核心数量
*2
。