Java中提供了synchronized,为什么还要提供Lock呢?

Java中提供了synchronized,为什么还要提供Lock呢?:

问题:

在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?今天,我们就一起来探讨下这个问题。
在Java 1.5版本中,synchronized的性能不如Lock,但在Java 1.6版本之后,synchronized做了很多优化,性能提升了不少。那既然synchronized关键字的性能已经提升了,那为何还要使用Lock呢?
  如果我们向更深层次思考的话,就不难想到了:我们使用synchronized加锁是无法主动释放锁的,这就会涉及到死锁的问题。

死锁问题

如果要发生死锁,则必须存在以下四个必要条件,四者缺一不可。
1.互斥条件
  在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。
  2.不可剥夺条件
  线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
  3.请求与保持条件
  线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。
  4.循环等待条件
  在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,pn等待p1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源

synchronized的局限性

如果我们的程序使用synchronized关键字发生了死锁时,synchronized关键是是无法破坏“不可剥夺”这个死锁的条件的。这是因为synchronized申请资源的时候, 如果申请不到, 线程直接进入阻塞状态了, 而线程进入阻塞状态, 啥都干不了, 也释放不了线程已经占有的资源。
  然而,在大部分场景下,我们都是希望“不可剥夺”这个条件能够被破坏。也就是说对于“不可剥夺”这个条件,占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源, 这样不可剥夺这个条件就破坏掉了。
  如果我们自己重新设计锁来解决synchronized的问题,我们该如何设计呢?

解决

了解了synchronized的局限性之后,如果是让我们自己实现一把同步锁,我们该如何设计呢?也就是说,我们在设计锁的时候,要如何解决synchronized的局限性问题呢?这里,我觉得可以从三个方面来思考这个问题。
 (1)能够响应中断。synchronized的问题是, 持有锁A后, 如果尝试获取锁B失败, 那么线程就进入阻塞状态, 一旦发生死锁, 就没有任何机会来唤醒阻塞的线程。但如果阻塞状态的线程能够响应中断信号, 也就是说当我们给阻塞的线程发送中断信号的时候, 能够唤醒它, 那它就有机会释放曾经持有的锁A。这样就破坏了不可剥夺条件了。
  (2)支持超时。如果线程在一段时间之内没有获取到锁, 不是进入阻塞状态, 而是返回一个错误, 那这个线程也有机会释放曾经持有的锁。这样也能破坏不可剥夺条件。
  (3)非阻塞地获取锁。如果尝试获取锁失败, 并不进入阻塞状态, 而是直接返回, 那这个线程也有机会释放曾经持有的锁。这样也能破坏不可剥夺条件。
  体现在Lock接口上,就是Lock接口提供的三个方法,如下所示。
  下面展示一些 内联代码片

  // 支持中断的API 
  void lockInterruptibly() throws InterruptedException; 
  // 支持超时的API 
  boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 
  // 支持非阻塞获取锁的API 
  boolean tryLock(); 
lockInterruptibly()

支持中断。

tryLock()

tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

tryLock(long time, TimeUnit unit)方法

tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
  也就是说,对于死锁问题,Lock能够破坏不可剥夺的条件,例如,我们下面的程序代码就破坏了死锁的不可剥夺的条件。

public class TansferAccount{ 
      private Lock thisLock = new ReentrantLock(); 
      private Lock targetLock = new ReentrantLock(); 
      //账户的余额 
      private Integer balance; 
      //转账操作 
      public void transfer(TansferAccount target, Integer transferMoney){ 
          boolean isThisLock = thisLock.tryLock(); 
          if(isThisLock){ 
              try{ 
                  boolean isTargetLock = targetLock.tryLock(); 
                  if(isTargetLock){ 
                      try{ 
                           if(this.balance >= transferMoney){ 
                              this.balance -= transferMoney; 
                              target.balance += transferMoney; 
                          }    
                      }finally{ 
                          targetLock.unlock 
                      } 
                  } 
              }finally{ 
                  thisLock.unlock(); 
              } 
          } 
      } 
  }   public class TansferAccount{ 
      private Lock thisLock = new ReentrantLock(); 
      private Lock targetLock = new ReentrantLock(); 
      //账户的余额 
      private Integer balance; 
      //转账操作 
      public void transfer(TansferAccount target, Integer transferMoney){ 
          boolean isThisLock = thisLock.tryLock(); 
          if(isThisLock){ 
              try{ 
                  boolean isTargetLock = targetLock.tryLock(); 
                  if(isTargetLock){ 
                      try{ 
                           if(this.balance >= transferMoney){ 
                              this.balance -= transferMoney; 
                              target.balance += transferMoney; 
                          }    
                      }finally{ 
                          targetLock.unlock 
                      } 
                  } 
              }finally{ 
                  thisLock.unlock(); 
              } 
          } 
      } 
  } 

例外,Lock下面有一个ReentrantLock,而ReentrantLock支持公平锁和非公平锁。
  在使用ReentrantLock的时候, ReentrantLock中有两个构造函数, 一个是无参构造函数, 一个是传入fair参数的构造函数。fair参数代表的是锁的公平策略, 如果传入true就表示需要构造一个公平锁, 反之则表示要构造一个非公平锁。如下代码片段所示。

//无参构造函数: 默认非公平锁 
  public ReentrantLock() { 
   sync = new NonfairSync(); 
  }  
  //根据公平策略参数创建锁 
  public ReentrantLock(boolean fair){ 
   sync = fair ? new FairSync() : new NonfairSync(); 
  } 

锁的实现在本质上都对应着一个入口等待队列, 如果一个线程没有获得锁, 就会进入等待队列, 当有线程释放锁的时候, 就需要从等待队列中唤醒一个等待的线程。如果是公平锁, 唤醒的策略就是谁等待的时间长, 就唤醒谁, 很公平;如果是非公平锁, 则不提供这个公平保证, 有可能等待时间短的线程反而先被唤醒。 而Lock是支持公平锁的,synchronized不支持公平锁。
  最后,值得注意的是,在使用Lock加锁时,一定要在finally{}代码块中释放锁,例如,下面的代码片段所示。

  try{ 
      lock.lock(); 
  }finally{ 
      lock.unlock(); 
  }

注:其他synchronized和Lock的详细说明,小伙伴们自行查阅即可。
转: http://www.51testing.com/html/80/15326880-4474152.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Javasynchronized和ReentrantLock都是用于实现线程同步的机制,但它们有一些不同之处。 1. 锁的获取方式:synchronized是隐式锁,它在代码块或方法上加上synchronized关键字后,线程进入代码块或方法时会自动获取锁,并在退出时释放锁。而ReentrantLock是显式锁,需要手动调用lock()方法获取锁,并在使用完毕后调用unlock()方法释放锁。 2. 锁的可重入性:synchronized是可重入锁,即同一个线程可以多次获取同一个锁,而不会造成死锁。ReentrantLock也是可重入锁,并且提供了更灵活的重入性,可以通过设置公平性来决定锁的获取顺序。 3. 锁的公平性:synchronized是非公平锁,即线程获取锁的顺序是不确定的。而ReentrantLock可以通过构造函数传入参数来设置为公平锁或非公平锁,默认为非公平锁。公平锁会按照线程的请求顺序来获取锁,而非公平锁则允许插队。 4. 锁的灵活性:ReentrantLock相比synchronized提供了更多的灵活性。例如,ReentrantLock可以通过tryLock()方法尝试获取锁,如果锁已被其他线程占用,则返回false,而synchronized没有类似的方法。此外,ReentrantLock提供了Condition接口,可以通过Condition实现更灵活的线程等待和唤醒机制。 总的来说,synchronizedJava语言内置的关键字,使用简单,但功能相对有限。而ReentrantLock是一个类,提供了更多的功能和灵活性,但使用起来相对复杂一些。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值