锁
一,基本概念
节约篇幅,锁(Lock)是什么就不说了,先列出几个重要的概念。
锁级别:分为对象级和类级,常见的大部分锁是对象锁;所有(或者绝大部分)的类和对象都自带一把隐藏的锁,这也是synchronized能锁任意对象和类的根本所在。每个对象有自己的对象锁,所有对象共用一个类锁。
可重入性:假设条件:锁住的方法中调用另一个锁住的方法,且两个锁是同一个对象。结果:如果该锁具有可重入性,则自动识别放行,常见的大部分锁都是这种类型;如果该锁不具备可重入性,则产生死锁。
互斥和非互斥:互斥就是同一时刻只能有一个线程访问,常见的大部分锁都是这种类型;非互斥就是(!互斥),比如读写锁(ReadWriteLock)能支持多个线程同时读,就不是互斥锁。
公平和非公平:公平就是遵循先到先得(FIFO)的原则,非公平则竞争获取,看cpu的心情。
二,体系结构
需要注意的是,Lock和ReadWriteLock是两个不同的接口,对应两个不种不同的体系结构。ReadWriteLock的JDK实现类ReentrantReadWriteLock内置一个ReadLock和一个WriteLock。非重入性的锁我从JDK1.7以及网络都没找到,以后遇到再补充,也希望高手补充下。
三,常见实例
关于synchronized:
Lock其实是一个独立的概念,但是在简单的应用中我们常常使用synchronized关键字来解决对象多线程同步访问的问题。synchronized可以锁整个方法,也可以锁某个小范围的过程。
public class SynTest
{
// 对象级别锁,锁住当前对象,也就是this
public synchronized void syn1(){
// do something
}
// 类级别锁,锁住当前对象的类,也就是SynTest.class
public synchronized static void syn2(){
// do something
}
// 对象级别锁,锁住当前对象,也就是this
public void syn3(){
synchronized(this)
{
// do something
}
}
// 对象级别锁,锁住list实例
public void syn4(){
List<String> list = new ArrayList<String>();
synchronized(list)
{
// do something
}
}
// 类级别锁,锁住SynTest.class,跟syn2()一样
public void syn5(){
synchronized(SynTest.class)
{
// do something
}
}
// 对象级别锁,锁住当前对象,也就是this
public synchronized void syn6(){
// 线程运行到这里的时候,this对象把锁交给该线程,syn1要求获取this锁才行运营,this锁具有可重入性,所以syn1也可以执行
syn1();
}
}
synchronized其实是一种隐式的上锁,本质上也是对Lock的操作,只需要理解到底是哪个对象被锁住了即可。
自定义锁:
synchronized提供了一种便捷的上锁方式,我们有时候还会自定义锁对象,因为自定义锁对象的一些高级特性是synchronized无法拥有的,而且应用更灵活。这里主要介绍ReentrantLock和ReentrantReadWriteLock。
ReentrantLock:
结合我遇到的实际情况来说明吧,我们有一个场景是对线路做监控,如果线路调度连续失败的次数超过一定的阀值(基本都是该线路失联了),则智能切换到其他线路。代码结构如下,可以看到tryLock方法不阻塞其他线程的特性。
public class Watcher
{
private static final int ERROR_THRESHOLD = 100;
private static Integer failCount = 0;
private static ReentrantLock lock = new ReentrantLock();
// 失败次数递增
public static void add()
{
synchronized(failCount)
{
failCount++;
}
if(failCount > ERROR_THRESHOLD)// 连续失败次数大于切换阀值
{
lock.tryLock();
try
{
// 只能有一个线程执行切换,切换代码省略
}catch(Exception e)
{
e.printStackTrace();
}finally
{
lock.unlock();
}
}
}
// 失败次数重置
public static void reset()
{
synchronized(failCount)
{
failCount = 0;
}
}
}
ReentrantReadWriteLock:
这个是一个非互斥锁,特性是只能有一个线程可以写,写的时候不能读,读的时候不能写,读的时候还能读。看到这个特性,就知道它是为操作一些需要常常读取,但是很少需要修改的数据而定制的。从我实际遇到的情况来说,就是关键字过滤功能,关键字库一般初始化好放内存中,只在很少的情况下需要去修改这个内存。
public class KeywordService
{
private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 关键字匹配
public static void matchKeyword(String msg)
{
lock.readLock().lock();
try
{
// 执行匹配
}catch(Exception e)
{
e.printStackTrace();
}finally
{
lock.readLock().unlock();
}
}
// 重置关键字到内存
public static void reloadKeyword()
{
lock.writeLock().lock();
try
{
// 重载关键字到内存
}catch(Exception e)
{
e.printStackTrace();
}finally
{
lock.writeLock().unlock();
}
}
}