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\trylock(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的线程仅仅了解了部分线程创建和同步相关的小部分知识点,但是其实在编程过程中遇到的事情并不仅仅限于此,所以进行整理,列表如下:

posted @ 2021-10-15 16:58  l.w.x  阅读(676)  评论(0编辑  收藏  举报