Java锁概念基础

  Java中的锁不管是Lock还是synchronized都可以分为互斥锁和非互斥锁。

 

  互斥锁只能被一个线程持有,其他线程只能等待锁的释放。synchronized,ReentrantLock,ReadWriteReentrantLock的WriteLock是互斥的,但ReadLock不是互斥的。

  FileLock可以设置为互斥锁或者非互斥锁。

 

  实现锁时可以基于操作系统的调度,也可以以自旋的形式来实现。

  利用操作系统的指令,让线程等待,当锁可用时,让线程醒过来。这种适合需要等待长时间的。如果等待的时间短,这个操作的代价是较大的。

  用循环不断的轮询锁的状态,锁可用的时候就退出。这就是自旋锁。这样里面基本不做什么事情的循环是非常耗CPU的,如果等待锁的时间很长,用这种方式是不合适的。

 

  自旋锁是JVM实现的,下面的例子可以简单的描述自旋锁

public class MyWaitNotify3{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

  public void doWait(){
    synchronized(myMonitorObject){
      while(!wasSignalled){
        try{
                         
   }
catch(InterruptedException e){...} } //clear signal and continue running.   ...
wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }

  没有其他的线程调用doNotify之前,doWait将一直自旋,等待wasSignalled变为true。

 

  自旋锁的缺点:

  1.自旋锁一直占用CPU,他在未获得锁的情况下,一直运行自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。

  2.在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁。

 

  可以使用-XX:+UseSpinning来打开自旋锁,使用-XX:PreBlockSpin来设置等自旋待的次数。

 

  有些时候我们会在完全没必要的情况下用到了锁,可以使用逃逸分析和锁消除来提升系统的性能。

  例如,下面的局部变量StringBuffer完全用不到加锁,反而会影响性能。 

public String createNewString(String a,String b){
    StringBuffer sb = new StringBuffer();
    return  sb.append(a).append(b);
}

  逃逸分析和锁消除可以使用-XX:+DoEscapeAnalysis和-XX:+EliminateLocks。锁消除需要JVM工作在server模式下。

 

  可重入锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。ReentrantLock 和synchronized 都是可重入锁。  

public class Test implements Runnable{

    public synchronized void get(){
        System.out.println(Thread.currentThread().getId());
        set();
    }

    public synchronized void set(){
        System.out.println(Thread.currentThread().getId());
    }

    @Override
    public void run() {
        get();
    }
    public static void main(String[] args) {
        Test ss=new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    }
}

 

  独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

   Atomic类型的数据类型,如AtomicIntegerAtomicLongAtomicReferenceAtomicBoolean等都可使用的是乐观锁。Atomic类型的实现是基于CAS算法的,由于需要同步的地方极少,也被称为轻量级锁。

 

  分布式系统上还有分布式锁等。

   

  对于多线程来说,死锁是非常严重的系统问题,必须修正。除了死锁,遇到很多的就是活跃度问题了。 活跃度问题主要包括:饥饿,丢失信号,和活锁等。 

  饥饿是指线程需要访问的资源被永久拒绝,以至于不能在继续进行。 比如说:某个权重比较低的线程可能一直不能够抢到CPU周期,从而一直不能够被执行。

  也有一些场景是比较容易理解的。对于一个固定大小的连接池中,如果连接一直被用完,那么过多的任务可能由于一直无法抢占到连接从而不能够被执行。这也是饥饿的一种表现。

 

  丢失信号比较好理解,thread1因为conditionA被wait,thread2因为conditionB被wait,另一线程调用notify而非notifyAll,thread1被唤醒,实际是conditionB满足了,应该是thread2被唤醒.这样thread1,thread2都无法正常运行。

  

  活锁(Livelock)是指线程虽然没有被阻塞,但是由于某种条件不满足,一直尝试重试,却终是失败。  

  线程间的协同也有可能导致活锁。例如如果两个线程发生了某些条件的碰撞后重新执行,那么如果再次尝试后依然发生了碰撞,长此下去就有可能发生活锁。  

  解决活锁的一种方案是对重试机制引入一些随机性。例如如果检测到冲突,那么就暂停随机的一定时间进行重试。这回大大减少碰撞的可能性。

 

posted on 2015-07-19 12:01  lnlvinso  阅读(499)  评论(0编辑  收藏  举报