精华内容
下载资源
问答
  • 今天小编就为大家分享一篇关于Java重入锁的实现原理与应用场景,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • java重入锁与不可重入锁

    万次阅读 多人点赞 2018-08-28 11:08:58
    synchronized  ReentrantLock 都是可重入锁。 可重入锁的意义在于防止死锁。 实现原理是通过为每个关联一个请求计数器一个占有它的线程。当计数为0时,认为是未被占有的;线程请求一个未被占有的时,...

    所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。

    synchronized 和   ReentrantLock 都是可重入锁。

    可重入锁的意义在于防止死锁。

    实现原理是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 。

    如果同一个线程再次请求这个锁,计数将递增;

    每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。

    关于父类和子类的锁的重入:子类覆写了父类的synchonized方法,然后调用父类中的方法,此时如果没有重入的锁,那么这段代码将产生死锁(很好理解吧)。

    例子:

    比如说A类中有个方法public synchronized methodA1(){

            methodA2();

    }

    而且public synchronized methodA2(){

                        //具体操作

    }

    也是A类中的同步方法,当当前线程调用A类的对象methodA1同步方法,如果其他线程没有获取A类的对象锁,那么当前线程就获得当前A类对象的锁,然后执行methodA1同步方法,方法体中调用methodA2同步方法,当前线程能够再次获取A类对象的锁,而其他线程是不可以的,这就是可重入锁。

     

    代码演示:

    不可重入锁:

    public class Lock{
        private boolean isLocked = false;
        public synchronized void lock() throws InterruptedException{
            while(isLocked){    
                wait();
            }
            isLocked = true;
        }
        public synchronized void unlock(){
            isLocked = false;
            notify();
        }
    }

    使用该锁:

    public class Count{
        Lock lock = new Lock();
        public void print(){
            lock.lock();
            doAdd();
            lock.unlock();
        }
        public void doAdd(){
            lock.lock();
            //do something
            lock.unlock();
        }
    }

    当前线程执行print()方法首先获取lock,接下来执行doAdd()方法就无法执行doAdd()中的逻辑,必须先释放锁。这个例子很好的说明了不可重入锁。

    可重入锁:

    接下来,我们设计一种可重入锁

    public class Lock{
        boolean isLocked = false;
        Thread  lockedBy = null;
        int lockedCount = 0;
        public synchronized void lock()
                throws InterruptedException{
            Thread thread = Thread.currentThread();
            while(isLocked && lockedBy != thread){
                wait();
            }
            isLocked = true;
            lockedCount++;
            lockedBy = thread;
        }
        public synchronized void unlock(){
            if(Thread.currentThread() == this.lockedBy){
                lockedCount--;
                if(lockedCount == 0){
                    isLocked = false;
                    notify();
                }
            }
        }
    }

    所谓可重入,意味着线程可以进入它已经拥有的锁的同步代码块儿。

    我们设计两个线程调用print()方法,第一个线程调用print()方法获取锁,进入lock()方法,由于初始lockedBy是null,所以不会进入while而挂起当前线程,而是是增量lockedCount并记录lockBy为第一个线程。接着第一个线程进入doAdd()方法,由于同一进程,所以不会进入while而挂起,接着增量lockedCount,当第二个线程尝试lock,由于isLocked=true,所以他不会获取该锁,直到第一个线程调用两次unlock()将lockCount递减为0,才将标记为isLocked设置为false。

    可重入锁的概念和设计思想大体如此,Java中的可重入锁ReentrantLock设计思路也是这样。

     

    synchronized和ReentrantLock 都是可重入锁。ReentrantLock与synchronized比较:

    1.前者使用灵活,但是必须手动开启和释放锁

    2.前者扩展性好,有时间锁等候(tryLock( )),可中断锁等候(lockInterruptibly( )),锁投票等,适合用于高度竞争锁和多个条件变量的地方

    3.前者提供了可轮询的锁请求,可以尝试去获取锁(tryLock( )),如果失败,则会释放已经获得的锁。有完善的错误恢复机制,可以避免死锁的发生。

    摘自:JAVA可重入锁与不可重入锁   和   Java不可重入锁和可重入锁理解

     

    展开全文
  • Java不可重入锁和可重入锁理解

    万次阅读 多人点赞 2018-06-28 15:30:35
    最近正在阅读Java ReentrantLock源码,始终对可重入和不可入概念理解不透彻,进行学习后记录在这里。 基础知识 Java多线程的wait()方法和notify()方法 这两个方法是成对出现和使用的,要执行这两个方法,有一个...

    最近正在阅读Java ReentrantLock源码,始终对可重入和不可重入概念理解不透彻,进行学习后记录在这里。

    基础知识

    Java多线程的wait()方法和notify()方法

    这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出IllegalMonitorStateException异常,所以这两个方法必须在同步块代码里面调用。

    wait():阻塞当前线程

    notify():唤起被wait()阻塞的线程

    不可重入锁

    所谓不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。我们尝试设计一个不可重入锁:

    public class Lock{
        private boolean isLocked = false;
        public synchronized void lock() throws InterruptedException{
            while(isLocked){    
                wait();
            }
            isLocked = true;
        }
        public synchronized void unlock(){
            isLocked = false;
            notify();
        }
    }

    使用该锁:

    public class Count{
        Lock lock = new Lock();
        public void print(){
            lock.lock();
            doAdd();
            lock.unlock();
        }
        public void doAdd(){
            lock.lock();
            //do something
            lock.unlock();
        }
    }

    当前线程执行print()方法首先获取lock,接下来执行doAdd()方法就无法执行doAdd()中的逻辑,必须先释放锁。这个例子很好的说明了不可重入锁。

    可重入锁

    接下来,我们设计一种可重入锁

    public class Lock{
        boolean isLocked = false;
        Thread  lockedBy = null;
        int lockedCount = 0;
        public synchronized void lock()
                throws InterruptedException{
            Thread thread = Thread.currentThread();
            while(isLocked && lockedBy != thread){
                wait();
            }
            isLocked = true;
            lockedCount++;
            lockedBy = thread;
        }
        public synchronized void unlock(){
            if(Thread.currentThread() == this.lockedBy){
                lockedCount--;
                if(lockedCount == 0){
                    isLocked = false;
                    notify();
                }
            }
        }
    }

    所谓可重入,意味着线程可以进入它已经拥有的锁的同步代码块儿。

    我们设计两个线程调用print()方法,第一个线程调用print()方法获取锁,进入lock()方法,由于初始lockedBy是null,所以不会进入while而挂起当前线程,而是是增量lockedCount并记录lockBy为第一个线程。接着第一个线程进入doAdd()方法,由于同一进程,所以不会进入while而挂起,接着增量lockedCount,当第二个线程尝试lock,由于isLocked=true,所以他不会获取该锁,直到第一个线程调用两次unlock()将lockCount递减为0,才将标记为isLocked设置为false。

    可重入锁的概念和设计思想大体如此,Java中的可重入锁ReentrantLock设计思路也是这样

    展开全文
  • Java并发编程:用AQS写一把可重入锁

    千次阅读 多人点赞 2018-11-12 02:32:37
    Java并发编程:自己动手写一把可重入锁 详述了如何用synchronized同步的方式来实现一把可重入锁,今天我们来效仿ReentrantLock类用AQS来改写一下这把。要想使用AQS为我们服务,首先得弄懂三个问题:AQS是什么?AQS...

    前一篇博客Java并发编程:自己动手写一把可重入锁详述了如何用synchronized同步的方式来实现一把可重入锁,今天我们来效仿ReentrantLock类用AQS来改写一下这把锁。要想使用AQS为我们服务,首先得弄懂三个问题:AQS是什么?AQS已经做了什么以及我们还需要做些什么?

    AQS简介

    AQS是J.U.C包下AbstractQueuedSynchronizer抽象的队列式的同步器的简称,这是一个抽象类,它定义了一套多线程访问共享资源的同步器框架,J.U.C包下的许多同步类实现都依赖于它,比如ReentrantLock/Semaphore/CountDownLatch,可以说这个抽象类是J.U.C并发包的基础。

    之所以把这一章节叫做AQS简介而不是叫AQS详解,是因为已经有大神写过详解的文章Java并发之AQS详解,这篇文章对AQS的源码解析很透彻,博主读了之后受益匪浅,鉴于对原作者的尊重,所以如上附上原文的链接。要想弄懂AQS还得从这一图说起。
    在这里插入图片描述
    如上图所述,AQS维护了一个state变量和一个FIFO先进先出队列,这个state用来干嘛的可以参考我前一篇博客中的那个count计数器,就是用来计数线程的重入次数的。上一篇博客还用了一个变量currentThread来记录已经获得这把锁的线程。而我们的AQS用的是一个先进先出的等待队列的完成这件事。当新的线程进来的时候,AQS调用tryAquice()方法试图去获得锁,如果获得的话,则调用interupt中断方法;如果没有获得锁,则把当前线程放入排队的队列,AQS队列不断的自旋尝试去判断已经占用的线程是否已经放开,如果锁依然被线程继续占用,则继续添加进等待队列。

    源码如下:

    public final void acquire(int arg) {
         if (!tryAcquire(arg) &&
             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
             selfInterrupt();
     }
    

    那个addWaiter方法,此方法用于将当前线程加入到等待队列的队尾,并返回当前线程所在的结点。

    private Node addWaiter(Node mode) {
        //以给定模式构造结点。mode有两种:EXCLUSIVE(独占)和SHARED(共享)
        Node node = new Node(Thread.currentThread(), mode);
        
        //尝试快速方式直接放到队尾。
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        
        //上一步失败则通过enq入队。
        enq(node);
        return node;
    }
    

    我们以独占式的同步帮助器为例来看一下AQS的执行流程。
    在这里插入图片描述
    大致流程如下:

    1. 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
    2. 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
    3. acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
    4. 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

    上述的流程和步骤已经是AQS帮我们实现了的功能,估计我讲的也不太清楚,这里再次推荐读者阅读这篇文章Java并发之AQS详解,下面我们应该来看看如何使用AQS。

    用AQS写一把互斥锁

    互斥锁是为了保证数据的安全,在任一时刻只能有一个线程访问该对象。由上一个小节我们可知,AQS已经为我们实现所有排队和阻塞机制,我们只需要调用getState()、setState(int) 和 compareAndSetState(int, int) 方发来维护state变量的数值和调用setExclusiveOwnerThread/getExclusiveOwnerThread来维护当前占用的线程是谁就行了。翻越JDK提供的API,它建议我们:应该将子类定义为非公共内部帮助器类,可用它们来实现其封闭类的同步属性。类 AbstractQueuedSynchronizer 没有实现任何同步接口。而是定义了诸如 acquireInterruptibly(int) 之类的一些方法,在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法。

    什么意思呢?意思就是建议我们:如果你想要使用AQS实现一把互斥锁Mutex,就必须先用一个类去继承AbstractQueuedSynchronizer这个抽象类,然而这个实现的子类(暂取名叫Sync)应该是作为Mutex的内部类来用的,提供给Mutex当作帮助器来使用。那么Lock接口,Mutex互斥锁,AbstractQueuedSynchronizer抽象类和Sync帮助器这四者存在什么联系呢?为了避免你听糊涂了,下面我整理他们的UML类图如下。
    Mutex图类

    由上图可知:Mutex互斥锁继承了Lock锁的接口,具有锁的属性,可以提供上锁和释放锁的方法,他是对外提供服务的服务者,而Mutex类有个Sync类型的私有对象sync,这个私有对象继承了AbstractQueuedSynchronizer抽象类,是Mutex锁和AQS的桥梁,是加锁和释放锁真正的服务者。如果你看明白了上面的UML类图,那么我们的Mutex互斥锁的定义应该如下:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.AbstractQueuedSynchronizer;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    public class Mutex implements Lock {
    	private Sync sync = new Sync();
    	
    	private class Sync extends AbstractQueuedSynchronizer {
    
    		@Override
    		protected boolean tryAcquire(int arg) {
    			// TODO Auto-generated method stub
    			return super.tryAcquire(arg);
    		}
    
    		@Override
    		protected boolean tryRelease(int arg) {
    			// TODO Auto-generated method stub
    			return super.tryRelease(arg);
    		}
    	}
    
    	@Override
    	public void lock() {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void lockInterruptibly() throws InterruptedException {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public boolean tryLock() {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public void unlock() {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public Condition newCondition() {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    }
    

    这里我们实现的是独占式的锁,Sync帮助器只需要覆盖父类的tryAcquire(),tryRelease()方法就行了,其他方法可以暂时删掉,如共享式的tryAcquireShared(),tryReleaseShared(),已经Condition用到的isHeldExclusively()和toString()方法都可以暂时不用实现,因为我们只是想先用AQS来做一把可以保证数据安全的锁,考虑的问题暂时没有那么多。

    /**
     * 互斥锁
     * @author 张仕宗
     * @date 2018.11.9
     */
    public class Mutex implements Lock{
    	//AQS子类的对象,Mutex互斥锁用它来工作
    	private Sync sync = new Sync();
    	
    	//Sync同步器类作为公共内部帮助器,可用它来实现其封闭类的同步属性
    	private class Sync extends AbstractQueuedSynchronizer {
    
    		@Override
    		protected boolean tryAcquire(int arg) {
    			assert arg == 1; //这里用到了断言,互斥锁,锁只能被获取一次,如果arg不等于1,则直接中断
    			if(this.compareAndSetState(0, 1)) { //这里做一下判断,如果state的值为等于0,立马将state设置为1
    				//返回true,告诉acqure方法,获取锁成功
    				return true;
    			}
    			return false;
    		}
    
    		@Override
    		protected boolean tryRelease(int arg) {
    			//释放锁,由于这是一把互斥锁,state不是0就是1,所以你需要做两步:
    			//1.直接将state置为0
    			this.setState(0);
    			
    			//返回true,告诉aqs的release方法释放锁成功
    			return true;
    		}
    	}
    
    	/**
    	 * 上锁的方法
    	 */
    	@Override
    	public void lock() {
    		sync.acquire(1);
    	}
    	
    	/**
    	 * 释放锁的方法
    	 */
    	@Override
    	public void unlock() {
    		sync.release(1);
    	}
    	
    	@Override
    	public void lockInterruptibly() throws InterruptedException {
    		// TODO Auto-generated method stub
    	}
    
    	@Override
    	public boolean tryLock() {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public Condition newCondition() {
    		// TODO Auto-generated method stub
    		return null;
    	}
    }
    

    上诉代码实现了一把最简单的锁,我们只实现其lock()和unlock()方法,其他方法请暂时忽略,而lock()方法和unlock()方法是如何实现的呢?lock()方法调用了Sync帮助器对象的sync.acquire(1)方法,由于我们的帮助器Sync并没有实现这个方法,所以实际调用的是AQS的acquire()方法,而AQS这时候做了什么时呢?再来一次该方法的源码:

         if (!tryAcquire(arg) &&
             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
             selfInterrupt();
    

    次方法干的第一件事就是去调用tryAcquire()方法,这个方法需要Sync来实现,如果自己的Sync没有实现这个方法的话,父类会直接抛出UnsupportedOperationException这个异常。

    		@Override
    		protected boolean tryAcquire(int arg) {
    			assert arg == 1; //这里用到了断言,互斥锁,锁只能被获取一次,如果arg不等于1,则直接中断
    			if(this.compareAndSetState(0, 1)) { //这里做一下判断,如果state的值为等于0,立马将state设置为1
    				//返回true,告诉acqure方法,获取锁成功
    				return true;
    			}
    			return false;
    		}
    

    由于这是一把互斥锁,所以只能有同一时刻只能获得一次锁。代码中用到了assert断言,如果预获得锁的次数不是1,则中断。接下来if中判断state状态是否为0,如果state状态为0,则说明锁还没有被占用,那么我立刻占用这把锁,判断state当前值和设置state为1这两步用原子性操作的代码语句是this.compareAndSetState(0, 1),并立马放回true,这时候AQS获得返回值,获得锁成功。如果是第二个线程进来,if语句判断得到的值非0,则直接返回false,这时候AQS将新进来的线程放进FIFO队列排队。

    接下来看看Mutex的unlock()方法,该方法调用了sync.release(1),看看AQS这时候做了什么!

        public final boolean release(int arg) {
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }
    

    此方法是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。同样的,我们的同步器Sync需要去实现这个tryRelease方法,不然同样会抛出UnsupportedOperationException异常。Sync的tryRelease方法比较简单:

    		@Override
    		protected boolean tryRelease(int arg) {
    			//释放锁,由于这是一把互斥锁,state不是0就是1,所以你需要做两步:
    			//1.直接将state置为0
    			this.setState(0);
    			
    			//返回true,告诉aqs的release方法释放锁成功
    			return true;
    		}
    

    只需要设置state为0即可,由于这是一把互斥锁,state不是0就是1所以直接调用this.setSate(0)。

    用AQS写一把重入锁

    上诉的Mutex并非一把可重入锁,为了实现这把锁能够让同一线程多次进来,回忆一下上一篇博客中怎么实现的?当时的做法是在锁的lock()自旋方法中判断新进来的是不是正在运行的线程,如果新进来的线程就是正在运行的线程,则获取锁成功,并让计数器+1。而在释放锁的时候,如果释放锁的线程等于当前线程,让计数器-1,只有当计数器count归零的时候才真正的释放锁。同样的,用AQS实现的锁也是这个思路,那么我们的tryAcquice方法如下:

    		@Override
    		protected boolean tryAcquire(int arg) {
    			//如果第一个线程进来,直接获得锁,并设置当前独占的线程为当前线程
    			int state = this.getState();
    			if(state == 0) { //state为0,说明当前没有线程占用该线程
    				if(this.compareAndSetState(0, arg)) { //判断当前state值,第一个线程进来,立刻设置state为arg
    					this.setExclusiveOwnerThread(Thread.currentThread()); //设置当前独占线程为当前线程
    					return true; //告诉顶级aqs获取锁成功
    				}
    			} else { //如果是第二个线程进来
    				Thread currentThread = Thread.currentThread();//当前进来的线程
    				Thread ownerThread = this.getExclusiveOwnerThread();//已经保存进去的独占式线程
    				if(currentThread == ownerThread) { //判断一下进来的线程和保存进去的线程是同一线程么?如果是,则获取锁成功,如果不是则获取锁失败
    					this.setState(state+arg); //设置state状态
    					return true;
    				}
    			}
    			return false;
    		}
    

    tryAcquice()方法代码含义如注释所示,与Mutex互斥锁不同的是当state状态不为0时我们的逻辑处理,如果第二次进来的线程currentThread和正在独占的线程ownerThread为统一线程,第一步设置state增加1,第二步返回true给AQS。

    tryRelease()方法代码如下:

    		@Override
    		protected boolean tryRelease(int arg) {
    			//锁的获取和锁的释放是一一对应的,获取过多少次锁就释放多少次锁
    			if(Thread.currentThread() != this.getExclusiveOwnerThread()) {
    				//如果释放锁的不是当前线程,则抛出异常
    				throw new RuntimeException();
    			}
    			int state = this.getState()-arg;
    			//接下来判断state是否已经归零,只有state归零的时候才真正的释放锁
    			if(state == 0) {
    				//state已经归零,做扫尾工作
    				this.setState(0);
    				this.setExclusiveOwnerThread(null);
    				return true;
    			}
    			this.setState(state);
    			return false;
    		}
    

    tryRelease()首先是获取当前state的值,并对这个值进行欲判:如果当前值state减去sync.release()传来的参数归零,则真正的释放锁,那么我们要做的第一步是设置state为0,接着设置当前独占的线程为null,再然后返回true告诉AQS释放锁成功。如果如果当前值state减去sync.release()传来的参数归零,如果让state的值为state-arg相减之后的值。

    目前为此,我们以来了AQS框架来改写的重入锁代码如下:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.AbstractQueuedSynchronizer;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    /**
     * 用AQS实现的重入锁
     * @author 张仕宗
     * @date 2018.11.9
     */
    public class MyAqsLock implements Lock{
    	//AQS子类的对象,用它来辅助MyAqsLock工作
    	private Sync sync = new Sync();
    	
    	private class Sync extends AbstractQueuedSynchronizer {
    
    		@Override
    		protected boolean tryAcquire(int arg) {
    			//如果第一个线程进来,直接获得锁,并设置当前独占的线程为当前线程
    			int state = this.getState();
    			if(state == 0) { //state为0,说明当前没有线程占用该线程
    				if(this.compareAndSetState(0, arg)) { //判断当前state值,第一个线程进来,立刻设置state为arg
    					this.setExclusiveOwnerThread(Thread.currentThread()); //设置当前独占线程为当前线程
    					return true; //告诉顶级aqs获取锁成功
    				}
    			} else { //如果是第二个线程进来
    				Thread currentThread = Thread.currentThread();//当前进来的线程
    				Thread ownerThread = this.getExclusiveOwnerThread();//已经保存进去的独占式线程
    				if(currentThread == ownerThread) { //判断一下进来的线程和保存进去的线程是同一线程么?如果是,则获取锁成功,如果不是则获取锁失败
    					this.setState(state+arg); //设置state状态
    					return true;
    				}
    			}
    			return false;
    		}
    
    		@Override
    		protected boolean tryRelease(int arg) {
    			//锁的获取和锁的释放是一一对应的,获取过多少次锁就释放多少次锁
    			if(Thread.currentThread() != this.getExclusiveOwnerThread()) {
    				//如果释放锁的不是当前线程,则抛出异常
    				throw new RuntimeException();
    			}
    			int state = this.getState()-arg;
    			//接下来判断state是否已经归零,只有state归零的时候才真正的释放锁
    			if(state == 0) {
    				//state已经归零,做扫尾工作
    				this.setState(0);
    				this.setExclusiveOwnerThread(null);
    				return true;
    			}
    			this.setState(state);
    			return false;
    		}
    		
    		public Condition newCondition() {
    			return new ConditionObject();
    		}
    	}
    
    	/**
    	 * 上锁的方法
    	 */
    	@Override
    	public void lock() {
    		sync.acquire(1);
    	}
    	
    	/**
    	 * 释放锁的方法
    	 */
    	@Override
    	public void unlock() {
    		sync.release(1);
    	}
    	
    	@Override
    	public void lockInterruptibly() throws InterruptedException {
    		sync.acquireInterruptibly(1);
    	}
    
    	@Override
    	public boolean tryLock() {
    		//调用帮助器的tryAcquire方法,测试获取锁一次,不会自旋
    		return sync.tryAcquire(1);
    	}
    
    	@Override
    	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    		//调用帮助器的tryRelease方法,测试释放锁一次,不会子旋
    		return sync.tryRelease(1);
    	}
    
    	@Override
    	public Condition newCondition() {
    		//调用帮助类获取Condition对象
    		return sync.newCondition();
    	}
    }
    

    关于测试的案例,则需要读者你自己来验证了,写测试用例可以参考我前一篇文章Java并发编程:自己动手写一把可重入锁

    展开全文
  • 轻松学习java重入锁(ReentrantLock)的实现原理

    万次阅读 多人点赞 2016-08-28 14:17:46
    前言相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发...如果真是这样,而且你有兴趣了解,今天我将带领你轻松的学习下java中非常重要,也非常基础的可重入锁-ReentrantLock的实现机制。

    前言

    相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么实现的。如果真是这样,而且你有兴趣了解,今天我将带领你轻松的学习下java中非常重要,也非常基础的可重入锁-ReentrantLock的实现机制。


    听故事把知识掌握了

    在一个村子里面,有一口井水,水质非常的好,村民们都想打井里的水。这井只有一口,村里的人那么多,所以得出个打水的规则才行。村长绞尽脑汁,最终想出了一个比较合理的方案,咱们来仔细的看看聪明的村长大人的智慧。

    井边安排一个看井人,维护打水的秩序。

    打水时,以家庭为单位,哪个家庭任何人先到井边,就可以先打水,而且如果一个家庭占到了打水权,其家人这时候过来打水不用排队。而那些没有抢占到打水权的人,一个一个挨着在井边排成一队,先到的排在前面。打水示意图如下 :

    打水示意图

    是不是感觉很和谐,如果打水的人打完了,他会跟看井人报告,看井人会让第二个人接着打水。这样大家总都能够打到水。是不是看起来挺公平的,先到的人先打水,当然不是绝对公平的,自己看看下面这个场景 :

    同家人一起打水

    看着,一个有娃的父亲正在打水,他的娃也到井边了,所以女凭父贵直接排到最前面打水,羡煞旁人了。
    以上这个故事模型就是所谓的公平锁模型,当一个人想到井边打水,而现在打水的人又不是自家人,这时候就得乖乖在队列后面排队。

    事情总不是那么一帆风顺的,总会有些人想走捷径,话说看井人年纪大了,有时候,眼力不是很好,这时候,人们开始打起了新主意。新来打水的人,他们看到有人排队打水的时候,他们不会那么乖巧的就排到最后面去排队,反之,他们会看看现在有没有人正在打水,如果有人在打水,没辄了,只好排到队列最后面,但如果这时候前面打水的人刚刚打完水,正在交接中,排在队头的人还没有完成交接工作,这时候,新来的人可以尝试抢打水权,如果抢到了,呵呵,其他人也只能睁一只眼闭一只眼,因为大家都默认这个规则了。这就是所谓的非公平锁模型。新来的人不一定总得乖乖排队,这也就造成了原来队列中排队的人可能要等很久很久。

    java可重入锁-ReentrantLock实现细节

    ReentrantLock支持两种获取锁的方式,一种是公平模型,一种是非公平模型。在继续之前,咱们先把故事元素转换为程序元素。

    元素转换

    咱们先来说说公平锁模型:

    初始化时, state=0,表示无人抢占了打水权。这时候,村民A来打水(A线程请求锁),占了打水权,把state+1,如下所示:

    线程A获取锁

    线程A取得了锁,把 state原子性+1,这时候state被改为1,A线程继续执行其他任务,然后来了村民B也想打水(线程B请求锁),线程B无法获取锁,生成节点进行排队,如下图所示:

    线程B等待

    初始化的时候,会生成一个空的头节点,然后才是B线程节点,这时候,如果线程A又请求锁,是否需要排队?答案当然是否定的,否则就直接死锁了。当A再次请求锁,就相当于是打水期间,同一家人也来打水了,是有特权的,这时候的状态如下图所示:

    可重入锁获取

    到了这里,相信大家应该明白了什么是可重入锁了吧。就是一个线程在获取了锁之后,再次去获取了同一个锁,这时候仅仅是把状态值进行累加。如果线程A释放了一次锁,就成这样了:

    线程A释放一次锁

    仅仅是把状态值减了,只有线程A把此锁全部释放了,状态值减到0了,其他线程才有机会获取锁。当A把锁完全释放后,state恢复为0,然后会通知队列唤醒B线程节点,使B可以再次竞争锁。当然,如果B线程后面还有C线程,C线程继续休眠,除非B执行完了,通知了C线程。注意,当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除。

    非公平锁模型

    如果你已经明白了前面讲的公平锁模型,那么非公平锁模型也就非常容易理解了。当线程A执行完之后,要唤醒线程B是需要时间的,而且线程B醒来后还要再次竞争锁,所以如果在切换过程当中,来了一个线程C,那么线程C是有可能获取到锁的,如果C获取到了锁,B就只能继续乖乖休眠了。这里就不再画图说明了。

    其它知识点

    java5中添加了一个并发包, java.util.concurrent,里面提供了各种并发的工具类,通过此工具包,可以在java当中实现功能非常强大的多线程并发操作。对于每个java攻城狮,我觉得非常有必要了解这个包的功能。虽然做不到一步到位,但慢慢虚心学习,沉下心来,总能慢慢领悟到java多线程编程的精华。

    结束语

    可重入锁的实现会涉及到CAS,AQS,java内存可见性(volatile)等知识,为了避免大家直接被代码搞晕,故而想以最简单的方式把可重入锁进行抽象,讲明白其中的实现原理,这样看起源码也有个借鉴的思路,希望本篇能够帮助到你们。

    展开全文
  • Java中的可重入锁和不可重入锁

    千次阅读 2019-12-02 10:20:27
    的简单应用 用lock来保证原子性(this.count++这段代码称为临界区) 什么是原子性,就是不可分,从头执行到尾,不能被其他线程同时执行。 可通过CAS来实现原子操作 CAS(Compare and Swap): CAS操作需要输入...
  • 重入锁重入锁

    千次阅读 多人点赞 2019-08-09 19:06:09
    重入锁重入锁重入锁又称递归,是指同一个线程...Java中的ReentrantLocksynchronized都是可重入锁,可重入锁的一个优点就是可以避免死锁,下面结合源码分析 public class Wedget{ public synchroniz...
  • 文章目录ReentrantLock的介绍重入性的实现原理公平公平 ReentrantLock的介绍 ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个,支持重入性,表示能够对共享资源能够...
  • Java中的公平锁和非公平实现详解   在Java中实现的方式有两种,一种是使用Java自带的关键字synchronized对相应的类或者方法以及代码块进行加锁,另一种是ReentrantLock,前者只能是非公平,而后者是默认...
  • Java并发编程:自己动手写一把可重入锁

    千次阅读 多人点赞 2018-11-08 02:51:18
    Lock是一个接口,实现它的子类包括:可重入锁:ReentrantLock, 读写中的只读:ReentrantReadWriteLock.ReadLock读写中的只写:ReentrantReadWriteLock.WriteLock 。我们先来用一用ReentrantLock可重入锁来...
  • Java中的公平锁和非公平实现详解

    万次阅读 多人点赞 2017-04-24 16:09:18
    Java语言中有许多原生线程安全的数据结构,比如`ArrayBlockingQueue`、`CopyOnWriteArrayList`、,它们的实现方式并非通过`...本博客着重讲述ReentrantLock的可重入性原理、公平/公平实现、内存可见性原理。
  • 什么是可重入锁以及在Java中的应用

    千次阅读 2018-04-23 00:14:55
    重入锁,顾名思义是指可以重复获取。对于一个线程,在外部获取后进行内部方法执行时同样可以再次获取到,不用阻塞等待的释放。可重入锁的意义一定程度上避免了死锁。 在Java中,可以使用关键字...
  • 关于Java 重入锁的理解

    千次阅读 2018-02-19 17:41:42
    Lock有一个实现类 ReentrantLock (又名可重入锁),这种是可以反复多次进入的,其局限性在于同一个线程内 1public interface Lock { 2 void lock();//获得,如果已经被占用,则等待 3 4 void ...
  • Java中的可重入锁

    千次阅读 2019-05-28 13:01:29
    本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock。 可重入锁,也叫做递归,指的是同一线程 外层函数获得之后 ,内层递归函数仍然有获取该的代码,但不受影响。 在JAVA环境下 ...
  • Java中,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译 之后,会在同步块的前后分别形成...据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为
  • Java synchronized 可重入锁 基本概念

    千次阅读 2017-02-07 19:04:58
    多线程 java中有几种方法可以实现一个线程?
  • Java中可重入锁-ReentrantLock基本上就是按照上面的思路来实现的,我们来对给他们做一个简单的对比。 一次只有一个人能打水 需要保证多线程的同步。 必须严格按照排队顺序打水 ReentrantLock提供的公平...
  • Java的ReentrantLock的源码实现,包括加锁、解锁的源码,以及公平性、重入性的实现!
  • 重入锁: 同一时刻只能有一条线程拥有重入锁,但此线程可以重复获得。其余需求获得此的线程被阻塞。代码实现关键是记录当前获得的线程。 public class ReentranceLock { //记录当前获得的线程。 ...
  • Java多线程编程-(1)-线程安全和锁Synchronized概念 基本介绍了进程线程的区别、实现多线程的两种方式、线程安全的概念以及如何使用Synchronized实现线程安全,下边介绍一下关于Synchronized的其他基本特性。 ...
  • 【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解
  • 重入锁详解(什么是可重入

    万次阅读 多人点赞 2019-04-26 15:17:26
    重入锁详解 ...// 演示可重入锁是什么意思,可重入,就是可以重复获取相同的,synchronizedReentrantLock都是可重入的 // 可重入降低了编程复杂性 public class WhatReentrant { public sta...
  • 重入锁简单理解就是对同一个线程而言,它可以重复的获取。例如这个线程可以连续获取两次,但是释放的次数也一定要是两次。下面是一个简单例子: public class ReenterLock { private static ...
  • 这种的使用方式和Java本身框架中的Reentrant Lock一模一样 RLock lock = redisson.getLock("testLock"); try{ // 1. 最常见的使用方法 //lock....
  • 重入锁和不可重入锁概念区别

    万次阅读 2019-01-21 16:09:58
    重入锁就是一个类的A、B两个方法,A、B都有获得统一把,当A方法调用时,获得,在A方法的还没有被释放时,调用B方法时,B方法也获得该。 这种情景,可以是不同的线程分别调用这个两个方法。也可是同一个...
  • Java重入锁与其释放

    千次阅读 2017-08-26 20:45:25
    在学习Java多线程相关的知识时,通常都会涉及这个概念,常见的synchronized、Lock均为可重入锁。为了更好的理解可重入锁,需要先理解一下几个问题: 1、谁持有了? 2、的对象是谁? 3、可重入锁的可重入是...
  • 重入和不可入的概念是这样的:当一个线程获得了当前实例的,并进入方法A,这个线程在没有释放这把的时候,能否再次进入方法A呢? 可:可以再次进入方法A,就是说在释放前此线程可以再次进入方法A...
  • 究竟什么是可重入锁

    万次阅读 多人点赞 2017-10-22 23:20:41
    经历很久之前就听说了可重入锁,可重入锁究竟是什么意思,以前是囫囵吞枣的,只要记住ReentrantLocksychronized是可重入锁就行了,爱咋用咋用,好吧,原谅我的无知,最近对基础查漏补缺,发现竟然对其一问三不知,...
  • 基于Redis的可重入锁,这个版本通过spring注入方式完成的Service版本,有时间写一个Util的版本。 1.定义的Lock接口,与JDK内置的类似,添加了过期时间,没有支持Condition。 import java.util.concurrent....
  • Java - 可重入锁ReentrantLock简单用法

    千次阅读 2018-04-24 19:16:45
    Java - 可重入锁ReentrantLock简单用法 Java 中显示的借口类主要位于java.util.concurrent.locks下,其主要的接口类有: 接口Lock,其主要实现为ReentrantLock 读写接口ReadWriteLock,其主要实现为...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 577,487
精华内容 230,994
关键字:

java重入锁和非重入锁

java 订阅