• JAVA篇:Java 多线程 (二) 线程锁机制和死锁


    2、线程锁机制和死锁

    关键字:Java锁分类名词、线程死锁、Lock、ReentrantLock、ReadWriteLock、Condition

    说到锁的话,总是会提到很多,其分类与理论部分应该会参考别人的描述,反正自己讲也不会比别人好。

    • 公平锁/非公平锁

    • 可重入锁

    • 独享锁/共享锁

    • 互斥锁/读写锁

    • 乐观锁/悲观锁

    • 分段锁

    • 偏向锁/轻量级锁/重量级锁

    • 自旋锁

    还有一部分则是Java中锁的实现与应用。

    • synchronized

    • Lock相关类

    • Condition相关类

    2.1 锁的分类名词

    前面所说的锁的分类名词,有的是指锁的状态、有的指锁的特性、有的指锁的设计。这部分主要是参考JAVA锁有哪些种类,以及区别(转)

    2.1.1 公平锁/非公平锁

    公平锁是指多个线程按照申请所的顺序来获取锁。

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能是后申请的线程比先申请的线程有限获取锁。有可能,会造成优先级反转或者饥饿现象。

    非公平锁的优点在于吞吐量比公平锁大。

    在Java中,synchronized是一种非公平锁。

    ReentrantLock则可以通过构造函数指定该锁是否公平锁,默认是非公平锁。ReentrantLock通过AQS来实现线程调度,实现公平锁。

    2.1.2 可重入锁

    可重入锁又名递归锁,是指在同一个线程在持有锁的前提下,再遇到需要申请同一个锁的情况时可自动获取锁。而非可重入锁遇到这种情况会形成死锁,也就是“我申请我已经持有的锁,我不会释放锁也申请不到锁,所以形成死锁。”

    Java中,

    synchronized在JDK 1.6优化后,属于可重入锁。

    ReentrantLock,即Re entrant Lock,可重入锁。

    synchronized void A(){
        System.out.println("A获取锁!");
        B();
    }
    synchronized void B(){
        System.out.println("B锁重入成功!");
    }

    2.1.3 独享锁/共享锁

    独享锁是指该锁一次只能被一个线程所持有,共享锁是指该锁可被多个线程所持有。

    在Java中,

    synchronized属于独享锁。

    ReentrantLock也属于独享锁。

    而Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证高效的并发读,但是读写、写读、写写的过程是互斥的,防止脏读、数据丢失。独享锁和共享锁也是通过AQS实现的。

    2.1.4 互斥锁/读写锁

    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

    互斥锁在Java中的具体实现就是ReentraLock

    读写锁在Java中的具体实现就是ReadWriteLock

    2.1.5 乐观锁/悲观锁

    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

    悲观锁认为对同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式,悲观地认为,不加锁的并发操作一定会出现问题。

    乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断更新的方式更新数据,乐观地认为,不加锁的并发操作是没有事情的。

    从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

    悲观锁在Java中的使用就是利用各种锁。

    乐观锁在Java中的使用就是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

    2.1.6 分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为segment,它类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道它要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不死放在一个酚酸中,就实现了真正的并行插入。

    但是在统计size的时候,即获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

    分段锁的设计目的就是细化锁的粒度,当操作不需要更新整个数组的时候,就针对数据的一项进行加锁操作。

    2.1.7 偏向锁/轻量级锁/重量级锁

    这三种所是指锁的状态,并且是针对synchronized。在 java 6通过引入锁的升级机制来实现高效synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。

    轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    2.1.8 自旋锁

    在Java中。自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

    2.2 线程死锁

    死锁是一个经典的多线程问题。避免死锁重要吗?一旦一组Java线程发生死锁,那么这组线程及锁涉及其已经持有锁的资源区将不再可用--除非重启应用。

    死锁是设计上的bug,它并不一定发生,但它有可能发生,而且发生的情况一般出现在极端的高负载的情况下。

    那么有什么办法为了避免死锁?

    1. 让程序每次至多只能获得一个锁。但这个在多线程环境下通常不现实。

    2. 设计时考虑清楚锁的顺序,尽量减少潜在的加锁交互数量

    3. 避免使用synchronized,为线程等待设置等待上限,避免无限等待。

    2.3 Lock和Condition

    JVM提供了synchronized关键字来实现对变量的同步访问以及用wait和notify来实现线程间通信。在jdk1.5以后,Java提供了Lock类来提供更加丰富的锁功能,并且还提供了Condition来实现线程间通信。

    Lock和Condition都属java.util.concurrent.locks下的接口,如下图所示。其中Lock的实现类包含ReentrantLockReadWriteLock,而Condition对象是通过lock对象创建的。

     

    其中的AbstractOwnableSynchronizer(AQS),即抽象的队列式的同步器,定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如ReentrantLock/Semaphore/CountDownLatch...

    在这里对AQS暂不做展开。Java并发之AQS详解

    2.4 ReentrantLock

    之前有说过ReentrantLock是一个可重入互斥的锁,在其构造函数中可以设置其是否是公平锁。

    /* 创建一个 ReentrantLock的实例。*/
    ReentrantLock()
    /* 根据给定的公平政策创建一个 ReentrantLock的实例。。*/
    ReentrantLock(boolean fair)

    相比于synchronized,ReentrantLock提供了更加丰富灵活的功能。

    1. void lock():获得锁。

    2. void unlock():尝试释放此锁。

    3. boolean tryLock():只有在调用时它不被另一个线程占用才能获取锁。

    4. boolean tryLock(long timeout, TimeUnit unit):有限制超时的请求锁,如果线程没有被中断且没有超时则可以获得锁。

    5. void lockInterruptibly() : 获取锁,除非被中断。

    6. Condition newCondition():返回一个Condition实例。

    7. boolean isFair():该锁是否为公平锁。

    8. boolean isHeldByCurrentThread():查询此锁是否由当前线程持有。

    9. boolean isLocked():查询此锁是否由任何线程持有。

    10. int getHoldCount():查询当前线程对此锁的阻塞数量。

    11. protected Thread getOwner():返回当前拥有此锁的线程,如果不拥有,则返回 null 。

    12. protected Collection<Thread> getQueuedThreads():返回可能正在等待获取此锁的线程的集合。

    13. int getQueueLength():返回等待获取此锁的线程数的估计。

    14. boolean hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁。

    15. boolean hasQueuedThreads():查询是否有线程正在等待获取此锁。

    16. protected Collection<Thread> getWaitingThreads(Condition condition):返回正在等待(wait)给定Condition的线程集合,传入的Condition必须与锁相关联。

    17. int getWaitQueueLength(Condition condition):返回正在等待(wait)给定Condition的线程数量估计,传入的Condition必须与锁相关联。

    18. boolean hasWaiters(Condition condition):查询是否有任何线程等待(wait)给定Condition,传入的Condition必须与锁相关联。

       

    2.4.1 请求锁释放锁

    ReentrantLock除了提供与synchronized锁功能相似的无限阻塞的锁请求lock()/unlock(),还提供了可限制阻塞超时的tryLock()/tryLock(timeout),可以更加灵活地处理锁相关的操作,防止线程死锁。

    同时还可以查看当前锁持有、阻塞的情况。

    测试代码如下:

        /* 测试ReentrantLock锁请求与释放 */
        public void test1(){
            /* 创建锁实例ReentrantLock */
            Lock rlock = new ReentrantLock();
           // System.out.println(rlock.toString());
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
    ​
            /* 线程1从头到尾持有锁 */
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
    ​
                    rlock.lock();
                    System.out.println(df.format(new Date())+" part11:子线程1先持有锁rlock,然后休眠5S");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(df.format(new Date())+" part12:子线程1释放锁rlock");
                    rlock.unlock();
    ​
                }
            };
    ​
            Thread t1 = new Thread(r1);
            t1.start();
    ​
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    ​
            /* 线程2使用lock申请锁 */
            Runnable r2 = new Runnable() {
                @Override
                public void run() {
                    System.out.println(df.format(new Date())+" part21:子线程2使用lock()申请锁rlock,会阻塞");
                    rlock.lock();
                    System.out.println(df.format(new Date())+" part22:子线程2获得了锁rlock,然后释放锁");
                    rlock.unlock();
                }
            };
    ​
            Thread t2 = new Thread(r2);
            t2.start();
    ​
            /* 线程3使用trylock	rylock(timeout)申请锁 */
            Runnable r3 = new Runnable() {
                @Override
                public void run() {
                    System.out.println(df.format(new Date())+" part31:子线程3使用trylock()申请锁rlock");
                    if(rlock.tryLock()){
                        System.out.println(df.format(new Date())+" part32:子线程3获得了锁rlock,然后释放锁");
                        rlock.unlock();
                    }else{
                        System.out.println(df.format(new Date())+" part33:子线程3没有获得锁rlock,使用带1S超时的trylock申请锁");
    ​
                        try {
                            if(rlock.tryLock(1000, TimeUnit.MILLISECONDS)){
                                System.out.println(df.format(new Date())+" part34:子线程3获得了锁rlock,然后释放锁");
                                rlock.unlock();
                            }else{
                                System.out.println(df.format(new Date())+" part35:子线程3没有获得锁rlock,超时退出");
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
    ​
            Thread t3 = new Thread(r3);
            t3.start();
    ​
            /* 主线程查看当前锁与线程的情况 */
            System.out.println(df.format(new Date())+" 主线程查看-rlock是否为公平锁:"+((ReentrantLock) rlock).isFair());
            System.out.println(df.format(new Date())+" 主线程查看-rlock是否被任意线程持有:"+((ReentrantLock) rlock).isLocked());
            System.out.println(df.format(new Date())+" 主线程查看-查询是否有线程等待获取rlock:"+((ReentrantLock) rlock).hasQueuedThreads());
            System.out.println(df.format(new Date())+" 主线程查看-查询子线程2是否等待获取锁rlock:"+((ReentrantLock) rlock).hasQueuedThread(t2));
            System.out.println(df.format(new Date())+" 主线程查看-查询等待获取锁rlock的线程数估计:"+((ReentrantLock) rlock).getQueueLength());
    ​
           /* 主线程尝试获取锁 */
            rlock.lock();
            System.out.println(df.format(new Date())+" part01:主线程获得锁rlock");
            System.out.println(df.format(new Date())+" 主线程查看-rlock是否被主线程持有:"+((ReentrantLock) rlock).isHeldByCurrentThread());
            System.out.println(df.format(new Date())+" 主线程查看-查询当前线程对rlock的阻塞数量:"+((ReentrantLock) rlock).getHoldCount());
            rlock.lock();
            System.out.println(df.format(new Date())+" part02:主线程重入锁rlock");
            System.out.println(df.format(new Date())+" 主线程查看-rlock是否被主线程持有:"+((ReentrantLock) rlock).isHeldByCurrentThread());
            System.out.println(df.format(new Date())+" 主线程查看-查询当前线程对rlock的阻塞数量:"+((ReentrantLock) rlock).getHoldCount());
            rlock.unlock();
            rlock.unlock();
    ​
            System.out.println(df.format(new Date())+"测试结束。");
    ​
    ​
        }

    输出结果如下:

    2021-10-11 09:54:03:122 part11:子线程1先持有锁rlock,然后休眠5S
    2021-10-11 09:54:03:124 part21:子线程2使用lock()申请锁rlock,会阻塞
    2021-10-11 09:54:03:124 主线程查看-rlock是否为公平锁:false
    2021-10-11 09:54:03:125 主线程查看-rlock是否被任意线程持有:true
    2021-10-11 09:54:03:125 part31:子线程3使用trylock()申请锁rlock
    2021-10-11 09:54:03:125 主线程查看-查询是否有线程等待获取rlock:true
    2021-10-11 09:54:03:125 part33:子线程3没有获得锁rlock,使用带1S超时的trylock申请锁
    2021-10-11 09:54:03:125 主线程查看-查询子线程2是否等待获取锁rlock:true
    2021-10-11 09:54:03:125 主线程查看-查询等待获取锁rlock的线程数估计:1 #这里为什么会是1?
    2021-10-11 09:54:04:132 part35:子线程3没有获得锁rlock,超时退出
    2021-10-11 09:54:08:123 part12:子线程1释放锁rlock
    2021-10-11 09:54:08:123 part22:子线程2获得了锁rlock,然后释放锁
    2021-10-11 09:54:08:124 part01:主线程获得锁rlock
    2021-10-11 09:54:08:124 主线程查看-rlock是否被主线程持有:true
    2021-10-11 09:54:08:124 主线程查看-查询当前线程对rlock的阻塞数量:1
    2021-10-11 09:54:08:125 part02:主线程重入锁rlock
    2021-10-11 09:54:08:125 主线程查看-rlock是否被主线程持有:true
    2021-10-11 09:54:08:125 主线程查看-查询当前线程对rlock的阻塞数量:2
    2021-10-11 09:54:08:125测试结束。

    对结果有疑惑的地方在于getQueueLength()为什么会在线程2、3都等待着锁的情况下,结果是1?后面经验证,getQueueLength()仅统计lock()后阻塞的线程,trylock(timeout)的等待应该底层有所不同。

    2.4.2 中断与锁请求

    lockInterruptibly()与lock()都是锁请求方法,不过lockInterruptibly()提供了响应中断请求以及处理中断请求的功能,使得在进行锁请求时,线程可以被中断。

    测试代码如下所示:

        /* 测试中断与锁请求 */
        public void test3() {
            /* 创建锁实例ReentrantLock */
            Lock rlock = new ReentrantLock();
    ​
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
            /* 子线程1使用lock等待 */
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                        System.out.println(df.format(new Date()) + " part11:子线程1使用lock请求锁");
                        rlock.lock();
                        System.out.println(df.format(new Date()) + " part12:子线程1获得锁");
                        rlock.unlock();
                        System.out.println(df.format(new Date()) + " part13:子线程1释放锁");
                    } catch (InterruptedException e) {
                        System.out.println(df.format(new Date()) + " part19:子线程1被中断!");
                    }
    ​
                }
            };
    ​
            Thread t1 = new Thread(r1);
            t1.start();
    ​
            /* 子线程2使用lockInterruptibly() */
            Runnable r2 = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                        System.out.println(df.format(new Date()) + " part21:子线程2使用lockInterruptibly请求锁");
                        rlock.lockInterruptibly();
                        System.out.println(df.format(new Date()) + " part22:子线程2获得锁");
                        rlock.unlock();
                        System.out.println(df.format(new Date()) + " part23:子线程2释放锁");
    ​
                    } catch (InterruptedException e) {
                        System.out.println(df.format(new Date()) + " part29:子线程2被中断!");
                    }
    ​
                }
            };
    ​
            Thread t2 = new Thread(r2);
            t2.start();
    ​
            /* 主线程先持有锁使得其他子线程等待,然后在合适的时间中断两个线程 */
            System.out.println(" 主线程先持有锁使得其他子线程等待,然后在合适的时间中断两个线程 ");
    ​
            rlock.lock();
            System.out.println(df.format(new Date()) + " part01:主线程获得锁后休眠,防止在sleep的时候中断");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    ​
            System.out.println(df.format(new Date()) + " part02:主线程中断子线程1");
            t1.interrupt();
            System.out.println(df.format(new Date()) + " part03:主线程中断子线程2");
            t2.interrupt();
            System.out.println(df.format(new Date()) + " part04:主线程释放锁");
            rlock.unlock();
            System.out.println(df.format(new Date()) + " 测试结束");
    ​
    ​
        }

    结果如下,可以看出来子线程2成功被中断了,而子线程1中断之后并无反应:

     主线程先持有锁使得其他子线程等待,然后在合适的时间中断两个线程 
    2021-10-11 10:22:12:435 part01:主线程获得锁后休眠,防止在sleep的时候中断
    2021-10-11 10:22:12:539 part21:子线程2使用lockInterruptibly请求锁
    2021-10-11 10:22:12:539 part11:子线程1使用lock请求锁
    2021-10-11 10:22:12:647 part02:主线程中断子线程1
    2021-10-11 10:22:12:647 part03:主线程中断子线程2
    2021-10-11 10:22:12:647 part04:主线程释放锁
    2021-10-11 10:22:12:647 part29:子线程2被中断!
    2021-10-11 10:22:12:647 测试结束
    2021-10-11 10:22:12:647 part12:子线程1获得锁
    2021-10-11 10:22:12:647 part13:子线程1释放锁

    2.5 ReadWriteLock

    锁会导致线程阻塞,大大降低了多线程处理数据的效率。前面有提到过对于锁的优化,可以将读写分离,当只需要进行并发读的时候,并不需要进行加锁操作。

    ReadWriteLock是一个接口,它提供了一个读锁和一个写锁,其实现在 ReentrantReadWriteLock及其内部静态类 ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock。

    public interface ReadWriteLock {
        /**
         * Returns the lock used for reading.
         *
         * @return the lock used for reading.
         */
        Lock readLock();
     
        /**
         * Returns the lock used for writing.
         *
         * @return the lock used for writing.
         */
        Lock writeLock();
    }

    2.5.1 ReentrantReadWriteLock

    ReentrantReadWriteLock.ReadLockReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock都提供了基本的锁操作,lock()、trylock()、tryLock(long timeout, TimeUnit unit)、lockInterruptibly()、unlock()、newCondition()。其中WriteLock还包含方法isHeldByCurrentThread()和getHoldCount()。

    读锁和写锁的实例对象分别由ReentrantReadWriteLock的两个实例方法readLock()和writeLock()返回。两个锁成对,在申请锁的时候有一定的关联。

    读锁ReadLock申请锁、获取锁的前提是,同一ReentrantReadWriteLock的写锁不被其他线程占有。

    写锁WriteLock申请锁、获取锁的前提是,写锁及同一ReentrantReadWriteLock的读锁不被其他线程占有。

    2.5.2 读写锁测试代码

    读写锁主要是为了防止多线程操作文件是发生冲突,导致文件结果与预期不符,同时读锁又保证了并发读取数据时多线程效率问题。这里测试代码主要看锁的情况,有关于多线程读写后面再拓展讨论。

    测试代码如下,运行过程中可以看到偶尔会有多个线程同时进行读操作:

        /* 测试读写锁 */
        public  void test4(){
            /* 创建读写锁 */
            ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
            ReentrantReadWriteLock.ReadLock  readlock = lock.readLock();
            ReentrantReadWriteLock.WriteLock  writelock = lock.writeLock();
            Random random = new Random();
    ​
            class RThread extends Thread{
                private  int tag;
    ​
                public RThread(int tag, ReentrantReadWriteLock lock){
                    this.tag = tag;
    ​
                }
                @Override
                public void run(){
                    int n = 5;
                    while (n>0){
                        n--;
                        /*随机休眠
                        try {
                            Thread.sleep(random.nextInt(300));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }*/
    ​
                        readlock.lock();
                        System.out.println(String.format("读取线程%d:当前读锁数量:%d,是否写锁定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked()));
    ​
                        System.out.println(String.format("读取线程%d:进行读取......剩余次数:%d",this.tag,n));
                        readlock.unlock();
    ​
                    }
    ​
                }
            }
    ​
            class WThread extends Thread{
                private  int tag;
    ​
    ​
                public WThread(int tag,ReentrantReadWriteLock lock){
                    this.tag = tag;
                }
                @Override
                public void run(){
                    int n = 5;
                    while (n>0){
                        n--;
    ​
                        writelock.lock();
                        System.out.println(String.format("写入线程%d:当前读锁数量:%d,是否写锁定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked()));
                        System.out.println(String.format("写入线程%d:进行写入......剩余次数:%d",this.tag,n));
                        writelock.unlock();
    ​
                    }
    ​
                }
            }
    ​
            /*创建5个读线程,5个写线程*/
            int tlength = 5;
            Thread[] rthreads = new RThread[5];
            Thread[] wthreads = new WThread[5];
    ​
            for(int i=0;i<tlength;i++){
                rthreads[i] = new RThread(i,lock);
                rthreads[i].start();
                wthreads[i] = new WThread(i,lock);
                wthreads[i].start();
            }
    ​
            
    ​
        }

    2.6 Condition

    Condition将对象监视器方法(wait、notify和notifyAll)分解为不同的对象,Lock取代了synchronized,而Condition取代了对象监视器方法的使用。

    Condition与Lock实例绑定,通过newCondition()方法创建。包含如下方法:

    • void await():调用后当前线程进入等待,直到被唤醒或者被中断。

    • void awaitUninterruptibly():使当前线程等待直到被唤醒。该方法不会被中断。

    • boolean await(long time, TimeUnit unit):使当前线程等待直到被唤醒或被中断,或指定的等待时间过去,返回是否超时的判定。

    • long awaitNanos(long nanosTimeout):使当前线程等待直到被唤醒或中断,或指定的等待时间过去,返回剩余等待时间。

    • boolean awaitUntil(Date deadline):使当前线程等待直到发出信号或中断,或者指定的最后期限过去。

    • void signal():唤醒一个等待线程。

    • void signalAll():唤醒所有等待线程。

    需要注意的是:除了调用Condition的等待及唤醒方法,包括lock的方法lock.hasWaiters(condition)和lock.getWaitQueueLength(condition),使用condition相关方法前必须已经获得对应的lock,否则会报错“java.lang.IllegalMonitorStateException”

    测试代码如下:

        /* 测试Condition */
        public void test5(){
            /* 创建锁和condition */
            ReentrantLock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
    ​
            /* 创建等待子线程 */
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
    ​
    ​
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+": 获得锁并进入等待。");
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+": 被唤醒并获得锁,运行结束退出锁。");
                    lock.unlock();
                }
            };
            int tlength = 5;
            Thread[] threads = new Thread[tlength];
    ​
            for(int i=0;i<tlength;i++){
                threads[i] = new Thread(r1);
                threads[i].start();
            }
    ​
            /*  主线程进行唤醒线程 */
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    ​
    ​
            lock.lock();
            System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d",
                    lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
            System.out.println("主线程:signal()");
            condition.signal();
            System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d",
                    lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
            System.out.println("主线程:signal()");
            condition.signal();
            System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d",
                    lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
            System.out.println("主线程:signalAll()");
            condition.signalAll();
            System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d",
                    lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
    ​
            lock.unlock();
    ​
    ​
    ​
        }

    运行结果如下:

    Thread-0: 获得锁并进入等待。
    Thread-2: 获得锁并进入等待。
    Thread-3: 获得锁并进入等待。
    Thread-1: 获得锁并进入等待。
    Thread-4: 获得锁并进入等待。
    主线程:等待锁lock的线程数估计:0, 是否有线程正在等待condition:true,估计等待线程数:5
    主线程:signal()
    主线程:等待锁lock的线程数估计:1, 是否有线程正在等待condition:true,估计等待线程数:4
    主线程:signal()
    主线程:等待锁lock的线程数估计:2, 是否有线程正在等待condition:true,估计等待线程数:3
    主线程:signalAll()
    主线程:等待锁lock的线程数估计:5, 是否有线程正在等待condition:false,估计等待线程数:0
    Thread-0: 被唤醒并获得锁,运行结束退出锁。
    Thread-2: 被唤醒并获得锁,运行结束退出锁。
    Thread-3: 被唤醒并获得锁,运行结束退出锁。
    Thread-1: 被唤醒并获得锁,运行结束退出锁。
    Thread-4: 被唤醒并获得锁,运行结束退出锁。

    2.X 参考

    Java中Lock,tryLock,lockInterruptibly有什么区别? - wuxinliulei的回答 - 知乎 https://www.zhihu.com/question/36771163/answer/68974735

    Java并发编程:Lock

     

    0、JAVA多线程编程

    Java多线程编程所涉及的知识点包含线程创建、线程同步、线程间通信、线程死锁、线程控制(挂起、停止和恢复)。之前 JAVA篇:Java的线程仅仅了解了部分线程创建和同步相关的小部分知识点,但是其实在编程过程中遇到的事情并不仅仅限于此,所以进行整理,列表如下:

    当你深入了解,你就会发现世界如此广袤,而你对世界的了解则是如此浅薄,请永远保持谦卑的态度。
  • 相关阅读:
    linux系统下MySQL表名区分大小写问题
    linux下查看Mysql默认编码、修改默认编码
    mysql的下载及安装(windows)
    数据库进阶
    mysql练习
    数据库基础
    jQuery
    JavaScript 基础学习(二)
    344.Reverse String
    计蒜客课程数据结构(顺序表)
  • 原文地址:https://www.cnblogs.com/liwxmyself/p/15411813.html
Copyright © 2020-2023  润新知