• 基础篇系列,JAVA的并发包


    JAVA中主要锁

    synchronized

    Reentrantlock

    ReentrantReadWriteLock

    问题引入

    为什么需要锁?

    为什么JAVA有了synchronize还需要Reentrantlock和ReentrantReadWriteLock?

    synchronize和lock分别怎么实现同步快(原子性,一致性,禁重排序)?

    synchronize和lock分别怎么实现锁的优化,可重入锁,偏向锁?

    lock如何实现公平锁(synchronize是非公平锁)?

    为什么需要锁?

    目的:

    锁的目的是防止资源的竞争,主要从  原子性(一致性),可见性,防止处理重排序 三个方面来处理, volatile满足了后面两个特性,JAVA从两方面来实现锁

    为什么JAVA有了synchronize还需要Reentrantlock和ReentrantReadWriteLock?

    synchronized与ReentrantLock ,使用上看区别

    1, synchronize在获取锁阻塞的时候是不能打断的

    2, synchronize无超时机制,阻塞了的话只能一直阻塞造成死锁

    3,synchronize只能notify,wait,如果需要两个或以上条件就不能用了,如: JAVA阻塞队列的实现,需要用是否为空和是否已满两个条件来阻塞线程

    看lock相关的API就知道, 主要就是解决这几个问题

    方法名称 描述
    lock 获取锁,如果锁无法获取,那么当前的线程就变为不可被调度,直到锁被获取到
    lockInterruptibly 获取锁,除非当前线程被中断。如果获取到了锁,那么立即返回,如果获取不到,那么当前线程变得不可被调度,一直休眠直到下面两件事情发生:1、当前线程获取到了锁

    2、其他的线程中断了当前的线程

    tryLock 如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回false
    tryLcok(long time,TimeUnit unit) 在指定时间内尝试获取锁如果可以获取锁,那么获取锁并且返回true,如果当前的锁无法获取,那么当前的线程变得不可被调度,直到下面三件事之一发生:1、当前线程获取到了锁

    2、当前线程被其他线程中断

    3、指定的等待时间到了

    unlock 释放当前线程占用的锁
    newCondition 返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁
     

    那ReentrantReadWriteLock呢?

    读写锁用于读多写少的情况,即当一条线程获取写锁后,后面的读锁都被阻塞,等待获取写锁的线程完成释放。

    场景,如本地缓存失效,当需要去DB拿数据进行写入的操作,需要阻塞其它读的操作.

    当然,读写锁也是可以基于notifyAll和wait实现

    需要注意的是

    1. 如果无写锁,读是不阻塞,
    2. 持有读锁后,不能直接调用写锁的lock方法 ,否则会造成死锁

    synchronize和lock分别怎么实现同步快(原子性,一致性,禁重排序)?

    synchronized锁

    实现依赖

    原子性,可见性和重排序都是依靠指令。方法同步和代码块同步依靠Monitor指令,代码块同步是使用monitorenter和monitorexit指令实现

    锁信息保存在JAVA对象头里,准确说是Mark Word

    synchronize的阻塞,依靠几个队列,属于不公平锁(线程先CAS竞争锁,再进队列)

    ContentionList(LIFO)-->EntryList(LIFO)-->OnDeck-->Owner-->Wait Set  (http://www.cnblogs.com/lykm02/p/4516777.html )

    锁的转换方面

    无锁-->偏向锁-->轻量锁-->重量锁  (http://blog.csdn.net/xad707348125/article/details/47189107)

    synchronize和lock分别怎么实现锁的优化,可重入锁,偏向锁?

    偏向锁和可重入锁的实现

    可重入锁,即当本线程进入同一锁时可以进行多次上锁,当然也需要多次释放

    偏向锁,即当获取线程再次进入同步块时不需要再次竞争(CAS),当某个Core CAS成功时必然会引起总线风暴,这就是所谓的本地延迟,本质上偏向锁就是为了消除CAS,降低Cache一致性流量

    原理:

    1, 锁保存当前锁的线程,判断同一个线程时允许

    用一个计数器去记录当前重入的次数,当进入时计数器+1, 当释放锁时计算器-1, 当为0时表示可竞争

    synchronized 

    实现方式, 会在 Mark Word 存储获取锁线程的ID,然后栈帧中也存储线程ID,以后该线程再次进入同步块(同步方法)时不需要花费CAS了。 

    lock 

    实现方式,  用JAVA代码实现处理,跟踪下  lock()(NonfairSyncCAS获取锁失败)->acquire(AQS尝试获取锁)-->tryAcquire(nonfairTryAcquire)-->nonfairTryAcquire(Sync 如下处理):

       final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {  //再次判断是否是未锁状态,state为0为未有线程获取锁
                    if (compareAndSetState(0, acquires)) { //CAS再次竞争获取锁,此处是公平锁与非公平锁的区别
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                //这里是是实现偏向锁的关键,比较如果是当前锁就不进入CLH队列后面的竞争了
                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;
            }
    

      

    lock如何实现公平锁(synchronize是非公平锁)?

    synchronize本身是非公平锁,无公平性实现.

    lock非公平锁代码(详细如上)

    if (compareAndSetState(0, acquires)) {  //如锁被释放,是非公平锁的话,用CAS再次竞争获取锁

    公平锁此段代码如下:

      protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {//如锁被释放,必须当队列为空时才去CAS竞争锁  
                        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;
            }
    

      

    lock如何实现公平锁(synchronize是非公平锁)?

    synchronize本身是非公平锁,无公平性实现.

    lock非公平锁代码(详细如上)

    if (compareAndSetState(0, acquires)) {  //如锁被释放,是非公平锁的话,用CAS再次竞争获取锁

    公平锁此段代码如下:

       protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {//如锁被释放,必须当队列为空时才去CAS竞争锁  
                        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;
            }

    常见的锁实现原理总结

    自旋锁  

    多个线程一起用CAS去尝试获取一个共同的可见性(volatile)的变量,获取成功即为获取锁

    如 所有线程都运行

    /**
    	 * 自旋锁方式去实现阻塞
    	 * 
    	 * 缺点:无法实现公平性,如果大量使用会增加CPU的Cache一致性流量开销
    	 */
    	public static void CASLock() {
    		// 不断去获取CAS的锁,如成功表示获取锁成功
    		while (state.compareAndSet(0, 1)) {
    		}
    	}
    
    	public static void CASUnlock() {
    
    		if (!state.compareAndSet(1, 0)) {
    			// 释放锁异常
    			throw new RuntimeException();
    		}
    	}
    

      

    排队自旋锁 (Ticket Lock)

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class TicketLock {
       private AtomicInteger serviceNum = new AtomicInteger(); // 服务号
       private AtomicInteger ticketNum = new AtomicInteger(); // 排队号
    
       public int lock() {
             // 首先原子性地获得一个排队号
             int myTicketNum = ticketNum.getAndIncrement();
    
                  // 只要当前服务号不是自己的就不断轮询
           while (serviceNum.get() != myTicketNum) {
           }
    
           return myTicketNum;
        }
    
        public void unlock(int myTicket) {
            // 只有当前线程拥有者才能释放锁
            int next = myTicket + 1;
            serviceNum.compareAndSet(myTicket, next);
        }
    }

    CLH锁

    CLH是在前驱节点的属性上自旋, 

    组成一个队列后,每个节点都有保存当前节点获取锁的状态,和前一个节点的指向,获取锁的步骤

    1, 新加个节点,并把节点通过自旋指向tail节点

    2, 成功后,不停判断指向节点的锁状态,当前节点锁释放时获取锁

    3, 释放锁,改变自身的锁持有状态就行

    MCS锁

    而MCS是在本地属性变量上自旋。

    欢迎关注我的公众号, 一起来构建我们的知识体系

  • 相关阅读:
    NOI AC#62 color(树上操作)
    fenbi
    bert 压缩优化方向的论文
    bert 编程入门| bert 瘦身技巧
    行政法+刑法+民法
    Bert原理 | Bert油管视频学习法
    vscode的使用以及快捷键总结
    NG课程笔记学习记录
    古典文学+古曲+四大文明古国
    中国地理+地球上的水和大气
  • 原文地址:https://www.cnblogs.com/springsource/p/6555734.html
Copyright © 2020-2023  润新知