您的位置:首页 > 编程语言 > Java开发

java-多线程深入(六)锁

2015-08-19 07:48 651 查看
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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程