并发编程二:普通锁

并发编程之普通锁

介绍

锁是一种线程同步机制,和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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值