并发编程之普通锁
介绍
锁是一种线程同步机制,和synchronized同步代码块一样,但是锁具备一些更高级和` 细粒度的对同步的控制。
锁和syncronized同步方法/代码块本是相同的,它们的目的都是为了保证共享资源被安全的访问。
锁(lock)作为用于保护临界区(critical section)的一种机制,被广泛应用在多线程程序中。
无论是 Java 语言中的 synchronized 关键字,还是使用并发包的Lock实现,都是多线程应用开发人员手中强有力的工具。
但是强大的工具通常是把双刃剑,过多或不正确的使用锁,会导致多线程应用的性能下降。
备注:锁这种机制和理念并不是Java所特有的,这只是一种思想其它任何语言都可以实现。
Java Lock介绍和使用
从jdk1.5之后在java.util.concurrent.locks包下包含了几个锁的实现,所以我们能够很轻易的使用锁来控制同步,
我们无需自己实现锁的功能。但是我们任然需要知道如何使用他们,并且知道他们是如何实现依然非常的重要。
Lock接口定义
public interface Lock {
//获得锁资源方法
void lock();
//释放锁资源方法
void unlock();
//建立一个对象监视器
Condition newCondition();
//尝试获得锁资源
boolean tryLock();
//指定时间内尝试获取锁资源
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void lockInterruptibly() throws InterruptedException;
}
备注:使用锁最后一定要释放锁资源,否则资源就可能永远得不到释放。
典型使用锁代码:
Lock lock = ...;
lock.lock();
try {
//访问共享资源
} finally {
//释放共享资源
lock.unlock();
}
一个简单例子引出锁
定义一个Counter类,其有一个方法就是自增count值。如果多线程场景下访问可能就会出问题。因此需要最自增方法做同步处理。
/**
*
* @author xuyi
* @Time 2016年8月13日 上午8:52:05
* @类名 Counter
* @功能描述:一个简单的计算类,里面只包含一个自增方法和count属性
* @春风十里不如你
* @备注:
*/
public class Counter
{
// 统计数值
private int count;
// 自增方法
public int incr()
{
return (count++);// 或者把++写在前面
}
}
备注:如果单线程环境访问这个对象并不会有任何问题,但是如果多线程访问则可能就会出问题了,
因为incr方法并没有使用同步机制,导致多个线程可以同时访问这个方法,那么就会导致他们的计算结果不正确了。
因为该方法不是原子操作,所以没有同步机制还是会出现安全问题。
解决方案:
1、对共享资源进行同步
2、使操作变为原子性操作
3、将count申明为可见的原子性AtomicInteger类型然后再操作
这里我们只使用第一种方式来扩展锁知识。
使用synchronized同步机制
将上面的incr方法改为如下,使用synchronized同步代码块
// 自增方法
public int incr()
{
synchronized (this)
{
return (count++);// 或者把++写在前面
}
}
使用Lock锁
正如之前我们提到的Java已经给我们提供了很棒的Lock实现类,我们可以使用ReentrantLock对象来达到锁同步的目的。
public class Counter
{
// 统计数值
private int count;
// 申明一个ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
// 自增方法
public int incr()
{
lock.lock();//将资源上锁,保证资源访问时安全的
try
{
return (count++);// 或者把++写在前面
} finally
{
lock.unlock();//最终释放锁资源
}
}
}
自定义实现锁
虽然JDK给我们提供了ReentrantLock和ReentrantReadWriteLock这两个非常棒的锁实现,
大部分场景下我们都能使用它们来解决很多问题。但是我们不仅要会用,更应该知道它们背后锁实现的原理。
简单的锁实现
/**
*
* @author xuyi
* @Time 2016年8月13日 上午9:14:38
* @类名 SimpleLock
* @功能描述:实现锁的基本功能
* @春风十里不如你
* @备注:
*/
public class SimpleLock
{
// 是否获得锁的标识
private boolean isLocked = false;
// 获取锁资源
public void lock()
{
synchronized (this)
{
while (isLocked)
{// 如果锁资源已经被别的线程占有了,那么就在此等待
try
{
wait();
} catch (InterruptedException e)
{
}
}
// 如果锁资源目前没有被别的资源占有,那么将锁标识位设置为true,表示锁资源被当前线程占有。
isLocked = true;
}
}
// 释放锁资源
public void unlock()
{
synchronized (this)
{
// 将锁资源标识为空闲状态,然后通知其它等待线程来抢占资源
isLocked = false;
notify();
}
}
// 其实该锁实现还是借助了JVM的synchronized同步机制,只是进行了稍加封装。
}
备注:该简单锁实现是比较简单粗暴的,因为它没有考虑同个对象资源里面,方法可能会存在互相调用,而导致锁重入的情况。
/**
*
* @author xuyi
* @Time 2016年8月13日 上午9:24:04
* @类名 Resource
* @功能描述:存在锁重入的方法
* @春风十里不如你
* @备注:
*/
public class Resource
{
// 定义一个简单锁对象
private SimpleLock lock = new SimpleLock();
public void outter()
{
lock.lock();
try
{
// ...
inner();
// ...
} finally
{
lock.unlock();
}
}
public void inner()
{
lock.lock();
try
{
// ...
} finally
{
lock.unlock();
}
}
}
备注:对于这个对象如果调用outter方法,使用SimpleLock来处理那么就会导致死锁了,所以此时我们应该升级我们的简单锁了。
可重入锁实现
关于重入锁,我们可以从上面那个例子看出来其到底是什么意思。
Java中的synchronized同步块是可重入的。这意味着如果一个java线程进入了代码中的synchronized同步块,
并因此获得了该同步块使用的同步对象对应的管程上的锁,那么这个线程可以进入由同一个管程对象所同步的另一个java代码块。
ReentrantLock也是可重入锁的实现
/**
*
* @author xuyi
* @Time 2016年8月13日 上午9:34:01
* @类名 SimpleReentrantLock
* @功能描述:简单可重入锁的实现
* @春风十里不如你
* @备注:
*/
public class SimpleReentrantLock
{
// 是否占有锁资源的标识位
private boolean isLocked = false;
// 当前锁资源被哪个线程持有
private Thread lockedByThread = null;
// 某线程上锁次数
private int lockCount = 0;
public void lock()
{
synchronized (this)
{
Thread currentThread = Thread.currentThread();
while (isLocked && currentThread != lockedByThread)
{// 如果锁资源被占有了,并且当前线程并不是占有锁资源的线程。
try
{
wait();
} catch (InterruptedException e)
{
}
}
isLocked = true;
lockCount++;
lockedByThread = currentThread;
}
}
public void unlock()
{
if (Thread.currentThread() == this.lockedByThread)
{// 如果当前线程就是获得锁资源的线程,那么就lockCount进行自减.
lockCount--;
if (lockCount == 0)
{// 如果lockCount恢复到0时就释放资源
isLocked = false;
notify();
}
}
}
}
总结
锁的概念在很多场景都是会存在的不经事编程语言、数据库、操作系统、文件的其本质就是对共享资源的并发访问。
其实java提供的ReentrantLock实现已经非常棒了,多看看其源码实现原理有助于提升自己的对编程模型的认识。
参考
1、JDK源码实现
2、http://tutorials.jenkov.com/java-concurrency/locks.html
3、https://www.ibm.com/developerworks/cn/java/j-lo-lock/