zoukankan      html  css  js  c++  java
  • 08-多线程笔记-2-锁-3-Lock-2-Lock

    与synchronized的区别

    在使用synchronized关键字的情形下,假如占有锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程就只能一直等待,别无他法。这会极大影响程序执行效率。因此,就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间 (解决方案:tryLock(long time, TimeUnit unit)) 或者 能够响应中断 (解决方案:lockInterruptibly())),这种情况可以通过 Lock 解决。

    当多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。因此,需要一种机制来使得当多个线程都只是进行读操作时,线程之间不会发生冲突。同样地,Lock也可以解决这种情况 (解决方案:ReentrantReadWriteLock) 。

    可以通过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized无法办到的

    常用方法

    lock()

    lock()方法用来获取锁。如果锁已被其他线程获取,则进行等待。在前面已经讲到,如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用Lock必须在try…catch…块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

    Lock lock = ...;
    lock.lock();
    try{
      //处理任务
    }catch(Exception ex){
    
    }finally{
      lock.unlock();  //释放锁
    }
    

    tryLock() & tryLock(long time, TimeUnit unit)

    tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,也就是说,这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。
    tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

    void lockInterruptibly() throws InterruptedException;

    lockInterruptibly()方法能够中断等待获取锁的线程。当两个线程同时通过lock.lockInterruptibly()获取某个锁时,假若此时线程A获取到了锁,而线程B只有等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

    Condition newCondition();

    条件变量(java.util.concurrent.Condition),如果说ReentrantLock是synchronized的替代选择,Condition则是将wait、notify、notifyAll等操作转化为相应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。 
    详细介绍在AQS小节中进行说明

    ReentrantLock

    ReentrantLock,即可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法.
    可重入锁是指在同一个线程在一个临界区获取锁后,再进入该线程的其他临界区会自动获取锁(前提锁对象是同一个对象或者类对象)。ReentrantLock和synchronized都是可重入锁,可重入锁可以避免线程持有锁但又请求锁造成的死锁问题。

    ReetrantLock类图

    这里有一个锁获取的公平性问题,如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的。ReentrantLock提供了一个构造函数,能够控制这个锁是否是公平的。
    事实上公平的锁机制往往没有非公平的效率高,因为公平的获取锁没有考虑到操作系统对线程的调度因素,这样造成JVM对于等待中的线程调度次序和操作系统对线程的调度之间的不匹配。对于锁的快速且重复的获取过程中,连续获取的概率是非常高的,而公平锁会压制这种情况,虽然公平性得以保障,但是响应比却下降了,但是并不是任何场景都是以TPS作为唯一指标的,因为公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足

    NonfairSync

    非公平锁继承了ReetrantLock内部类Sync。在Sync中实现了非公屏锁获取的关键方法(JDK11):

    final boolean nonfairTryAcquire(int acquires) {
    	final Thread current = Thread.currentThread();
    	int c = getState();
    	if (c == 0) {
    		if (compareAndSetState(0, acquires)) {
    			setExclusiveOwnerThread(current);
    			return true;
    		}
    	}
    	else if (current == getExclusiveOwnerThread()) {
    		int nextc = c + acquires;
    		if (nextc < 0) // overflow
    			throw new Error("Maximum lock count exceeded");
    		setState(nextc);
    		return true;
    	}
    	return false;
    }
    

    当锁可用时,直接获取并返回获取结果;当线程相同时,直接重入,否则返回获取锁失败;

    FairSync

    公平锁也是集成自ReetrantLock的内部类Sync。在FairSync中实现了公平锁获取方法:

     protected final boolean tryAcquire(int acquires) {
    	final Thread current = Thread.currentThread();
    	int c = getState();
    	if (c == 0) {
    		if (!hasQueuedPredecessors() &&
    			compareAndSetState(0, acquires)) {
    			setExclusiveOwnerThread(current);
    			return true;
    		}
    	}
    	else if (current == getExclusiveOwnerThread()) {
    		int nextc = c + acquires;
    		if (nextc < 0)
    			throw new Error("Maximum lock count exceeded");
    		setState(nextc);
    		return true;
    	}
    	return false;
    }
    

    与非公平锁明显区别在于,当锁可用时,会使用hasQueuedPredecessors判断当前线程是否有获取锁的资格。

    public final boolean hasQueuedPredecessors() {
    	Node h, s;
    	if ((h = head) != null) {
    		if ((s = h.next) == null || s.waitStatus > 0) {
    			s = null; // traverse in case of concurrent cancellation
    			for (Node p = tail; p != h && p != null; p = p.prev) {
    				if (p.waitStatus <= 0)
    					s = p;
    			}
    		}
    		if (s != null && s.thread != Thread.currentThread())
    			return true;
    	}
    	return false;
    }
    

    hasQueuedPredecessors函数中,通过遍历AQS(队列同步器,下节进行详细说明)中等待队列,判断是否有比当前线程等待时间更长的线程,如果有,那么当前线程不具有获取锁的资格,返回true;如果当前线程是等待时间最长的线程,当前线程有获取锁的资格,返回false;

  • 相关阅读:
    算法第四章上机实验报告
    算法第四章作业
    算法第三章上机实验报告
    算法第三章作业
    算法第二章上机实验报告
    算法第二章作业
    第五次c++作业总结
    第三次c++作业总结
    Linux上部署Java项目
    JVM类加载
  • 原文地址:https://www.cnblogs.com/donfaquir/p/13846098.html
Copyright © 2011-2022 走看看