并发编程二:普通锁

来源:互联网 发布:yum mirrorlist 编辑:程序博客网 时间:2024/05/11 19:19

并发编程之普通锁

介绍

锁是一种线程同步机制,和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/

0 0
原创粉丝点击