Java锁机制

1.减少锁的竞争
  我们看到串行化会损害可伸缩性,上下文切换回损害性能。竞争性的锁会同时导致这两种所示,所以减少锁的竞争讷讷狗狗改进性能和可伸缩性。
  访问独占锁守护的资源是串行的——一次只能有一个行程访问它。当然,我们有很好的理由使用锁。
  并发程序中,对可伸缩性首要的威胁是独占的资源锁。有两个原因影响着锁的竞争性:锁被请求的频率,以及每次持有该所的时间
  如果这两者的乘积足够小,那么大多数请求所得尝试都是非竞争的,这样竞争性的锁不会成为可伸缩性巨大的阻碍。
  但是如果这个锁的请求量很大,线程将会阻塞以等待锁;在极端的情况下处理器将会闲置,即使仍有大量工作等着完成。
  有3种方式来减少锁的竞争:
  1、减少持有锁的时间(比如可以把与锁无关的代码移出synchronized块)
  2、减少请求所得频率
  3、或者用协调机制取代独占锁,从而允许更强的并发性
  减少锁的粒度,可以通过分拆锁和分离锁来实现。
 
 2.ReetrantLock
  在java5.0之前,用于调节共享对象访问的机制只有synchronized和volatile。Java5.0提供了新的选择:ReetrantLock。
  ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥和内存可见性的保证。
  获得ReentrantLock的锁与进入synchronized块有着相同的内存语义,释放ReentrantLock锁与退出synchronized块有相同的内存语义。
  ReentrantLock提供了与synchronized一样的可重入加锁的语义
  ReentrantLock支持Lock接口电一的所有获取锁的模式。
  与synchronized相比,ReentrantLock为处理不可用的锁提供了更多灵活性
  为什么要创建与内部锁如此相似的机制呢?内部锁在大部分情况下都能很好的工作,但是有一些功能上的局限——不能中断那些正在获取锁的线程,并且在请求所失败的情况下,必须无线等待。
  内部锁必须在获取他们的代码块中被释放;这很好的简化了代码,与异常处理机制能够进行良好的互动,但是在某些情况下,一个更灵活地加锁机制提供了更好的活跃度和性能。
  但是忘记finally释放是一个定时炸弹,这就是ReentrantLock不能完全替代。
Lock lock = new ReentrantLock();  
lock.lock();  
try {   
  // update object state  
}  
finally {  
  lock.unlock();   
}  

 

3.Synchronized

  把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有原子性(atomicity)和可见性(visibility)。

  原子性意味着一个线程一次只能执行由一个指定监控对象(lock)保护的代码,从而防止多个线程在更新共享状态时相互冲突。

  可见性则更为微妙,它要对付内存缓存和编译器优化的各种反常行为。

  一般来说,线程以某种不必让其他线程立即可以看到的方式(不管这些线程在寄存器中、在处理器特定的缓存中,还是通过指令重排或者其他编译器优化),不受缓存变量值的约束,但是如果开发人员使用了同步,如下面的代码所示,那么运行库将确保某一线程对变量所做的更新先于对现有synchronized 块所进行的更新,当进入由同一监控器(lock)保护的另一个 synchronized 块时,将立刻可以看到这些对变量所做的更新。类似的规则也存在于 volatile 变量上。

synchronized (lockObject) {   
  // update object state  
}  

 

 4.Synchronized 与 ReetrantLock

    ① ReentrantLock拥有Synchronized相同的并发性和内存语义,此外还多了锁投票,定时锁等候和中断锁等候等特性。

    线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定

    如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断

    如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

    

    ReentrantLock获取锁定与三种方式:

    lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

    tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

    tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

    lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

   ②synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行

    lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

     ③在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

 

    5.0的多线程任务包对于同步的性能方面有了很大的改进,在原有synchronized关键字的基础上,又增加了ReentrantLock,以及各种Atomic类。

    简单的总结

    synchronized:

      在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。

      原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。

    ReentrantLock:

      ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。

      在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

    Atomic:

      和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。

      激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。

      因为他不能在多个Atomic之间同步。

    所以,我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难。

 
 

总结来说,Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

 
 
 
posted @ 2017-02-14 17:33  novalist  阅读(1420)  评论(0编辑  收藏  举报