共享资源问题的解决方案(一)-----------加锁机制

      


       这个问题首先必须知道的是,多个任务同时访问同一个资源会出现什么问题?这是很关键的事情,因为如果我们不知道为什么会出现问题,而且也不知道问题是什么样子,就好像是明知对面浓雾里有致命的敌人,但是却无法知道敌人长什么样子,也不知道会从哪里冒出来,说真的,这种感觉很要命,一点也不好玩!那么,我们的敌人到底是谁呢?
       多个任务同时竞争同一个资源,就像两个大胖子抢一个浴缸,两辆车抢同一个车位,等等,在现实生活中,这些情况一定会发生严重的争执,而在程序中,就是在运行中,我们永远不知道到底是哪个任务得到资源。哦,真要命,作为程序员,最害怕的情况之一就是我们竟然无法得知自己程序的运行情况!相信每个程序员遇到这种情况一定会担忧得睡不着觉,要是放着不管,哪天夜里用户打个抱怨电话就会吓得跳起来。好吧,程序员就是容易紧张兮兮,好吧,我更正,是像我这样的新手容易紧张兮兮。那么,怎么解决这个问题呢?
       其中一个广泛使用的方法就是当资源被一个任务使用时,在其上加锁。加锁?什么意思?就是当一个大胖子使用浴缸的时候,马上就将门锁起来,管你外面怎么叫嚷,我就是不开,慢慢等我洗好再说!是的,就是这样,加锁的意思就是只有使用资源的任务使用完后,其他任务才能使用该资源。那为什么要这样呢?大家一起使用不是很好吗抢?不是两个大胖子一个小浴缸,是两个大胖子一起使用一个泳池呢?是的,这种理想情况当然是最好的,但是现实总是不理想的,往往我们手头上的资源是不允许两个任务同时使用,就好像两个输出同时输出会怎样呢?而且,任务在使用资源的时候,被其他任务打扰,是可能会出现问题的,就像是一个是递增的任务,一个是递减的任务,同时对同一个值进行操作,请问这个值是什么情况你能预料吗?所以,与其抱着不会出现问题这种虚幻的期望,还不如一开始就切切实实的解决问题。既然你们都是想要洗完澡,那么一个一个来不就好了!锁就能起到这样的作用。
       那么怎样加锁呢?通常是通过在代码前加上一条锁语句。如:
       Lock lock = new ReentrantLock();
       .....
       lock.lock();
      既然加了锁,当你这个任务结束这个资源的使用后,你就得开锁啊,总不能一直锁着不放自己却再也没用过啊!站着茅坑不拉屎,是会被爆出翔来的,于是就要释放锁。也是一句话:lock.unlock();
      但是要注意,上锁与开锁的动作必须确保它们能够在正确的时候进行,否则会出问题。如何保证它们能够正确执行?不要天真的认为,程序会严格按照你语句的顺序来执行,这是很不靠谱的信任。要是问题就是一句信任就能解决的话,那还需要警察吗!所以,我们就要使用一些方法来确保这样的动作,那就是try-finally的使用。
        lock.lock();
        try{
             ....
        }finally{
           lock.unlock();
        }
       这样就能确保任务的确是在加锁并对资源进行处理后,才释放锁的,不会出现过早释放锁的情况。
       这是使用显示的锁对象,java中还有内建的锁。关键字synchronized为防止资源冲突提供内置支持。当任务要执行synchronized关键字保护的代码片段的时候,将检查锁是否可用,然后获取锁,执行代码,释放锁。这里的过程必须详细解释是怎么进行,毕竟你一个关键字就将我前面的代码要解决的问题都解决了,是人都会这么想,有可能吗!
        可能就是这样来的。共享资源一般的形式是什么?大部分是以对象形式存在的内存片段,当然也可能是文件,输入/输出端口,或者是打印机。不管是怎样的形式,一般的情况下,我们都是将它包装进一个对象中,然后,把所有要访问这个资源的方法标记为synchronized。首先,这个资源我们一般设为private,这是很重要的,因为我们都知道,private资源只能由该类的方法来使用,而且所有对象上其实都自动含有单一的锁。当我们在该对象上调用其synchronized方法时,此对象就会被加锁。说白了,就是在java的世界中,所有的对象一出生,身上就带有一把锁,然后当它们被synchronized方法使用时,该锁就会锁上。说到这里,感觉java就是一个奴隶社会,对象就是奴隶,从出生到死亡,身上就带着锁。大家千万不要感到意外,因为想想我们平时使用对象时,无非也就是我们叫它做什么,它就做什么,其实跟奴隶主也没啥两样,只是这些奴隶偶尔会跟我们找些麻烦,所以,我们就要更加合理的使用它们,防止奴隶暴动啊。
       加了锁之后,该对象上其他synchronized方法只能等待这个锁被释放才能使用该对象。记住,是其他synchronized方法,而不是其他方法。所以,准确来说,是一个对象上所有的synchronized方法共享该对象上同一个锁。
       有趣的是,一个任务是可以多次获得该对象的锁。如果一个方法在同一个对象上调用第二个方法,后者又调用同一对象上的另一个方法,就会发生这种情况。JVM负责跟踪对象被加锁的次数。任务第一次给对象加锁的时候,计数为1,每当这个相同的任务在这个对象上获得锁时,就是调用synchronized方法,计数加1.当然,只有首先获得锁的对象才能允许继续获取多个锁。每当任务离开一个synchronized方法时,计数减1,当计数为0时,锁就被完全释放,此时别的任务就可以使用此资源。这里有个疑问,就是前面讲过,一个对象的synchronized方法获得该对象的锁后,其他synchronized方法是要等到这个方法释放锁才能使用该资源的,怎么现在可以多次获得该对象的锁。事实就是,可以获得多个锁,但没说,这些锁一起用啊?前面都说了,是一个一个的锁的释放,所以,根本就不存在所谓的矛盾啊。
       请原谅我在上面说些无意义的话,但是最好还是能够将所有的疑问都厘清比较好,所以就写了出来。
       按照惯例,当一个问题出现多种解决方案时,我们首先想到的第一个问题就是,该用哪种方案比较好,每种方案的优缺点和使用条件又是什么。
       使用显示的Lock对象的优点是要与其惯用法,即try-finally连在一起的,因为我们通常都是这样处理显示的Lock对象的。为什么我这么说,因为大家都知道,为什么使用try-finally?就是为了确保正确的工作状态。使用synchronized关键字时,如果某些事物失败了,就会抛出一个异常,而我们这时是没有机会去做任何清理工作来维护系统使其处于良好的状态,因为抛出异常,如果没有捕获异常,就会跳出当前的执行点。线程的异常又跟一般的异常不一样,这点,会做专门的介绍。但是如果有try-finally,就可以在finally子句里进行清理工作了。
       使用关键字synchronized,代码量显然大大减少,而且一般情况下都不会出现什么问题,所以,使用关键字synchronized是首选的方式。但是,必须要记住,synchronized也有自己无法应付的情况,比如说,尝试去获取锁并且最终获取锁失败,或者尝试获取锁一段时间,然后放弃它,这就必须使用显示的Lock对象,因为要使用到Lock的tryLock()方法。synchronized无法做到这一点,因为它根本就没有这些方法支持,它只是一个内置的关键字而已,你能奢望它还能做些什么呢?Lock对象是有一个库的支持,自然就会有各种方法来支持各种情况。所以,使用显示的Lock对象的情况,就是你想要有专门的同步结构,而不是默认的得到锁,等待锁,释放锁这些简单又只能执行其中一种的单一方案,像是上面这种情况。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值