Java中synchronized,java中synchronized的用法

  Java中synchronized,java中synchronized的用法

  00-1010 1.为什么提供锁接口?二。死锁问题三。同步IV的局限性。为了解决这个问题,摘要:在Java中提供了synchronized关键字,以确保只有一个线程可以访问同步代码块。既然已经提供了synchronized关键字,为什么还要在Java的SDK包中提供Lock接口?重复造轮子没必要吗?今天,我们一起来探讨这个问题。

  在Java中,提供synchronized关键字是为了确保只有一个线程可以访问同步代码块。既然已经提供了synchronized关键字,为什么还要在Java的SDK包中提供Lock接口?重复造轮子没必要吗?今天,我们一起来探讨这个问题。

  问题?

  既然JVM中提供了synchronized关键字来保证只有一个线程可以访问同步的代码块,为什么还要提供Lock接口呢?这是在反复做轮子吗?Java设计师为什么要这么做?带着疑问一起往下看。

  00-1010很多朋友可能听说过,在Java版本中,synchronized的性能不如Lock。但是Java版以后,synchronized做了很多优化,性能提升了很多。既然synchronized关键字的性能提高了,为什么还要用Lock?

  如果我们想得更深一点,就不难想到:使用同步锁是不能主动释放锁的,会涉及死锁。

  00-1010要发生死锁,必须存在以下四个必要条件,缺一不可。

  互斥条件在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。

  不可剥夺条件线程获取的资源在用完之前不能被其他线程强行拿走,也就是说只能由获取资源的线程自己释放(只能自愿释放)。

  请求与保持条件线程已经保留了至少一个资源,但是它提出了一个新的资源请求,并且该资源已被其他线程占用。此时,发出请求的线程被阻塞,但它继续持有已经获得的资源。

  当循环等待条件,出现死锁时,必然有一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占用的资源,P2等待P3占用的资源,…,Pn等待P1占用的资源,形成一个进程等待循环。循环中每个进程占用的资源同时被另一个应用程序使用,也就是说,前一个进程占用了后一个进程无意中占用的资源。

  00-1010如果在我们使用synchronized关键字的程序中出现死锁,synchronized关键字无法打破“不可分割”死锁的条件。这是因为当synchronized申请资源时,如果申请失败,线程会直接进入阻塞状态,当线程进入阻塞状态时,什么也做不了,已经被线程占用的资源也无法释放。

  但是,在大多数场景中,我们都希望“不可剥夺”的条件能够被破坏。也就是说,对于“不可分割”的条件,当占用部分资源的线程进一步申请其他资源时,如果申请失败,可以主动释放所占用的资源,从而破坏不可分割的条件。

  如果我们自己重新设计锁来解决同步问题,应该怎么设计?

  00-1010了解了synchronized的局限性后,如果要自己实现,应该如何设计一个synchronized锁?也就是说,我们在设计锁的时候,如何解决synchronized的局限性?在这里,我觉得可以从三个方面来思考这个问题。

  (1)能够响应中断。synchronized的问题是,在持有锁A之后,如果获取锁B的尝试失败,那么线程将进入阻塞状态。一旦死锁发生,就没有机会唤醒被阻塞的线程。但是,如果被阻塞的线程能够响应中断信号,也就是说,当我们向被阻塞的线程发送中断信号时,它能够唤醒它,它将有机会释放它曾经持有的锁A。这破坏了不可剥夺的条件。

  (2)支持超时。如果一个线程在一段时间内没有获得锁,而不是进入阻塞状态,它返回一个错误,那么这个线程也有机会释放它曾经持有的锁。这也会破坏不可剥夺的条件。

  (3)获得无阻塞的锁。如果试图获取锁

  败, 并不进入阻塞状态, 而是直接返回, 那这个线程也有机会释放曾经持有的锁。 这样也能破坏不可剥夺条件。

  体现在Lock接口上,就是Lock接口提供的三个方法,

  如下所示:

  

// 支持中断的APIvoid lockInterruptibly() throws InterruptedException;// 支持超时的APIboolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 支持非阻塞获取锁的APIboolean 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(); } } }}

例外,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的详细说明,小伙伴们自行查阅即可。

 

  

到此这篇关于Java中提供synchronized后为什么还要提供Lock的文章就介绍到这了,更多相关Java中 synchronized Lock内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: