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异常。
(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。
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/wangpeifeng669/article/details/42779591