java多线程中提供的锁:synchronized和lock。
(一)synchronized
1、synchronized的使用
每个对象都自带锁,锁可以同步实例方法(this是对象锁)、静态方法(class是对象锁)、方法块(synchronized参数是对象锁)
下面是锁住实例方法:
public synchronized void add(){
a++;
}
使用注意点:
(1)Object的wait、notify和notifyAll使用时需在代码外层加锁,等待和唤醒锁必须相同,使用的锁不能发生改变,不然会抛出IllegalMonitorStateException异常
/**
* 线程等待唤醒测试
*
* @author peter_wang
* @create-time 2014-10-9 下午2:50:36
*/
public class ThreadNotifyTest {
private static Integer num = new Integer(0);
/**
* @param args
*/
public static void main(String[] args) {
final Thread thead1 = new Thread() {
@Override
public void run() {
synchronized (num) {
try {
sleep(2000);
num.wait();
System.out.println("解锁成功");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thead1.start();
Thread thead2 = new Thread() {
@Override
public void run() {
try {
sleep(1000);
// num = new Integer(1); //A
num++;//B
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thead2.start();
}
}
无论执行A或B,改变了锁num后,wait执行的时候会抛出IllegalMonitorStateException异常。对wait、notify加锁是为了保证它们在执行中的原子性。
(2)使用的锁尽量是不可变对象,使用private final Object对象,可变化的对象可能导致不可预知的后果,如wait的问题。
(3)synchronized锁住区域尽量减少,提高性能。
2、synchronized原理探究
(1)线程状态
当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:
Contention List:所有请求锁的线程将被首先放置到该竞争队列
Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List,降低对Contention List的争用
Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set
OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck Owner:获得锁的线程称为Owner !Owner:释放锁的线程
(2)锁类型
公平锁和非公平锁
公平锁:每个线程取得调度的几率是一样的
非公平锁:每个线程取得的调度几率不同,是公平锁吞吐率的5-10倍
自旋锁和阻塞锁
自旋锁:线程阻塞调度过程设计到操作linux内核线程,需要在用户态和内核态之间切换状态,性能消耗比较大,自旋机制让请求调度的线程内部自循环,不切换状态等待一段时间,若仍然未能获取调度机会再转换锁类型
阻塞锁:阻塞锁在线程竞争时,无获取到调度的线程直接进入阻塞队列
多种阻塞锁类型
偏向锁:在大多数情况下,锁都存在于单线程中,为了让线程获得锁减少性能代价,同一线程多次重入,不会执行CAS操作,直到遇见多线程竞争,转换成其他类型
轻量锁:偏向锁的升级版或者直接设置系统不使用偏向锁直接进入轻量锁,比偏向锁多了步CAS操作,当前若锁未被其他线程锁住即可使用,操作失败进入自旋锁
重量锁:完整的阻塞锁状态,对象监视器(Monitor),由自旋锁超时升级而成
锁的进化过程:偏向锁—>轻量锁—>自旋锁—>重量锁
(3)总结
synchronized执行时,优先使用偏向锁或轻量锁提升性能,碰到多线程锁住现象,进入自旋状态,等待未果进入重量锁阶段,阻塞线程,放入阻塞队列,切换线程状态。
(二)Lock
1、ReentrantLock的使用
private ReentrantLock mlock = new ReentrantLock();
@Override
public void write() {
mlock.lock();
try {
long startTime = System.currentTimeMillis();
System.out.println("开始往这个buff写入数据…");
for (;;)// 模拟要处理很长时间
{
if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE)
break;
}
System.out.println("终于写完了");
}
finally {
mlock.unlock();
}
}
ReentrantLock在lock的时候锁住实例对象,必须在finally中unlock解锁,防止异常抛出未解锁。
2、ReentrantLock原理分析
ReentrantLock中的操作都是基于Sync,Sync继承自AbstractQueuedSynchronizer。
AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列容纳所有的阻塞线程,而对该队列的操作均通过Lock-Free(CAS)操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁的功能。
(三)synchronized和ReentrantLock对比
1、性能上synchronized是native方法性能优化较多,ReentrantLock是java层实现性能不一定很好。
2、ReentrantLock提供了更多功能,如公平锁和非公平锁设置等,但是需要使用finally解锁。
3、在普通情况下使用synchronized,在业务复杂需要使用ReentrantLock特殊功能的才使用ReentrantLock。