• 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)是指线程虽然没有被阻塞,但是由于某种条件不满足,一直尝试重试,却终是失败。  

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

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

  • 相关阅读:
    枚举8项素数和环
    登录过滤器
    线程调度
    回溯素数环
    centos 6.5 samba简单配置
    区间k大数查询
    Centos安装arm-linux-gcc等交叉工具链
    centos7安装tftp服务器
    八皇后问题
    输出1——n的排列(深度优先搜索)
  • 原文地址:https://www.cnblogs.com/lnlvinso/p/4658429.html
Copyright © 2020-2023  润新知