美文网首页MySQLJava 杂谈
Java中的锁与线程间通信

Java中的锁与线程间通信

作者: 张依一 | 来源:发表于2019-06-30 01:53 被阅读2次

    公平锁与非公平锁

    并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或者非公平锁,默认是非公平锁

    两者区别:

    公平锁:在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列中的第一个,就占用锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中等待被取到。

    非公平锁:非公平锁比较粗鲁上来就直接尝试占有锁,如果尝试失败,就在采用类似公平锁的方式在队列中等待被取到。

    对于java ReentrantLock而言,通过构造函数指定该锁是否为公平锁,默认是非公平锁。非公平锁的优点自安于吞吐量比公平锁大。

    对于synchronized而言,也是一种非公平锁

    可重入锁(递归锁)

    可重入锁又叫递归锁,典型的可重入锁有ReentrantLock/Synchronized。指的是统一鲜橙外层函数获得锁之后,内层度规函数仍然能获取该锁的代码,同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
    也就是说,线程可以进入任何一个他已经拥有的锁所同步着的代码块。

        public synchronized void method01(){
            method02();
        }
        public synchronized void method02(){
            
        }
    

    如以上代码,如果一个线程拥有method01的锁,那么会自动拥有method02的锁,这样看,可重入锁最大的作用是避免死锁

    自旋锁

    可参考上篇文章介绍的CAS的加锁方式

        public final int getAndSetInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var4));
    
            return var5;
        }
    

    自旋锁是指获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁直至成功为止,没有类似wait的阻塞,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

    手动实现一个自旋锁:

    public void myLock(){
            Thread thread = Thread.currentThread();
            System.out.println(thread +": come in lock");
            while (!atomicReference.compareAndSet(null,thread)){
    
            }
        }
    
    
    public void unLock(){
            Thread thread = Thread.currentThread();
            System.out.println(thread + ": come in unlock");
            while (!atomicReference.compareAndSet(thread,null)){
    
            }
        }
    

    当A线程调用myLock时,thread为null,所以atomicReference.compareAndSet(null,thread)成立,取反跳出循环,这时atomicReference内Thread为A的线程,B线程同时也调用myLock方法时,因为期望值为null,实际上是A线程,结果不成立,所以一直在while中循环;A线程再调用unlock方法,期望值是A自己的线程,然后设置为null,返回true然后取反,跳出循环,B线程不断地在mylock中循环,突然读到期望值为null了,满足自己的需求,然后跳出循环,这时加锁成功。

    独占锁(写锁)/共享锁(读锁)/互斥锁

    独占锁:该锁一次只能被一个线程所持有,ReentrantLock和Synchronized都是独占锁
    共享锁:该锁可被多个线程所持有。
    ReentrantReadWriteLock中的读锁是共享锁,其写锁是独占锁。
    读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

    CountDownLatch

    让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒
    CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞。其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会被阻塞),当计数器的值变成零时,因调用await方法被阻塞的线程会被唤醒,继续执行。

    CyclicBarrier

    CyclicBarrier意思Wie可循环使用的屏障。主要功能是让一组线程到达一个屏障点(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
    demo: 集齐7颗龙珠才能召唤神龙

    Semaphore

    Semaphore 信号量,主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

    Synchronized 和 Lock有什么区别? 用新的lock有什么好处

    1. 构成角度
      synchronized是关键字属于jvm层面,通过monitorenter(底层通过monitor对象来完成,wait/notify等方法也依赖于monitor对象,只有在同步块或同步方法中才能调wait/notify等方法)、monitorexit来控制锁
      Lock是具体的实现类(java.util.concurrent.locks.lock),是api层面的锁
    2. 使用方法
      synchronized 不需要用户手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用
      reentrantlock需要用户去手动释放锁,若没有主动释放锁,就有可能导致出现死锁现象。需要配合lock()和unlock()方法配合try/finally语句块来完成。
    3. 等待是否可中断
      synchronized不可中断,除非抛异常或者正常运行完成
      reentrantLock可中断:
      • 设置超时方法tryLock(long timeout,TimeUnit unit)
      • lockInterruptibly()放代码块中, 调用interrupt()方法可中断
    4. 加锁是否公平
      synchronized非公平锁
      reentrantlock默认非公平锁,构造方法可以传入bolean设置是否公平锁
    5. 绑定多个条件condition
      synchronized没有条件设置只能随机notify 或notifyAll
      reentrantlock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized,要么随即唤醒一个线程要么唤醒全部线程

    相关文章

      网友评论

        本文标题:Java中的锁与线程间通信

        本文链接:https://www.haomeiwen.com/subject/urhvcctx.html