为什么有多个线程获取到锁,不是应该只有一个可以获取锁么?其他的要等第一个释放才能获取

来源:6-5 J.U.C之AQS-ReentrantLock与锁-1

慕粉3349069

2018-06-03

运行结果如下:

//img.mukewang.com/szimg/5b132bf600015e4511060721.jpg

测试代码如下:请老师指导。   

 // 请求总数

    public static int clientTotal = 5000;


    // 同时并发执行的线程数

    public static int threadTotal = 100;


    public static int count = 0;


    private final static ReentrantLock lock = new ReentrantLock();


    public static void main(String[] args) throws Exception {

        ExecutorService executorService = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

        for (int i = 0; i < clientTotal ; i++) {

            executorService.execute(() -> {

                try {

                    add();

                } catch (Exception e) {

                    log.error("exception", e);

                }

            });

        }

        countDownLatch.await();

        executorService.shutdown();

        log.info("count:{}", count);

    }


    private static void add() {

        lock.lock();

        try {

            count++;

            System.out.println(count + "=====" + lock.getHoldCount());

        } finally {

//            lock.unlock();

        }

    }

=================================================

(谢谢老师的回答,学生现在学习和测试该功能)还烦老师再次指导。

第二次提问:

前提是add方法没有unlock释放锁。

http://img.mukewang.com/szimg/5b1394a90001d07607030230.jpg

有以下3种情况:

1、在线程池中加入sleep

http://img.mukewang.com/szimg/5b1393930001387510770421.jpg

执行结果一直保持1===========1。

2、在for循环中加入sleep

http://img.mukewang.com/szimg/5b1393f60001548511190479.jpg

会继续执行一段时间,但是不会执行到5000。

3、直接执行add()

http://img.mukewang.com/szimg/5b139474000144ac10500415.jpg

对于可重入锁的理解如下,

1、如果该锁不被另一个线程持有并立即返回,则将该锁设置为一个。(该点容易理解)

2、如果当前线程已经保存了锁,则保持计数递增1,并且方法立即返回。(针对上面3种情况的测试出现不同的结果,还烦请老师指导学生一下)

3、如果锁由另一线程持有,那么当前线程在线程执行调度时被禁用并且处于休眠状态,直到锁已被获取,此时锁保持计数被设置为一。(该点学生理解的是:该锁只能被一个线程调用(或者重复调用),第二个线程需要等待第一个锁释放才能获取;但是学生的疑问是,学生的第一次提问,执行结果为什么是有多个线程都获取到锁了?)

写回答

2回答

Jimin

2018-06-03

第一,不要通过修改题目来提其他问题,容易被我忽略,因为我点过去会发现我已经回复过。回过头看讨论过程也会怪怪的,变成我一直在补充回答。

第二,不要使用system.out的打印结果来验证多线程的输出结果,请使用课程里介绍的log或者自己定义一个log。
你这样输出的结果是无法具体确认是哪个线程执行的,尤其是你要分析的内容与哪个线程执行有关,而log在输出日志则会带上时间和实际执行的线程,这两项对结果的分析至关重要。
线程池在实际执行代码时,尤其是add这种简单操作时,很可能只拿出一个线程去执行就可以了。你问题里说的不同线程,你的根据是什么?

第三,说说你第2个case和第3个case的想法,我没看出想表达啥,尤其是第2个

第四,说一下遇到不懂的问题自己该怎么办。第一次运行时发现阻塞,你在肯定会阻塞相关的代码里加上断点,看看当时一些细节的数据,答案可能直接就出来了。

最后,多线程环境下输出的日志一定要带上线程名和执行时间,这些对分析至关重要

0
1
慕粉3349069
非常感谢!
2018-06-04
共1条回复

Jimin

2018-06-03

你好,这是因为ReentrantLock是可重入锁,当一个线程得到一个对象后,再次请求该对象锁时是可以再次得到该对象的锁的。具体就是自己可以再次获取自己的内部锁。

你可以打开ReentrantLock的lock方法的源码注释:

/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
   sync.lock();
}

这里写的还是很清楚,如果你想完全保证第一次执行完第二次再执行,那就使用synchronized修饰这个方法吧。

目前你这种写法,由于没有unlock,会导致资源一直得不到释放,方法一直无法结束,最后出现OutOfMemory异常。而且你这里调用的getHoldCount()方法通常一般只在测试时用。

关于ReentrantLock锁的一些说明及,也可以参考一些博客说明,比如:

https://www.jianshu.com/p/96c89e6e7e90

0
1
慕粉3349069
老师这个回答也很棒,只是只能选择一个采纳
2018-06-04
共1条回复

Java高并发编程,构建并发知识体系,提升面试成功率

构建完整并发与高并发知识体系,倍增高薪面试成功率!

3923 学习 · 832 问题

查看课程