Java 在语法层面已经有了 synchronized 来实现管程,为什么还要在 JDK 中提供了 Lock 和 Condition 工具类来做这样的事情,这属于重复造轮子吗?
首先你可能会想到的是 synchronized 性能问题,但是我想告诉你的是 synchronized 在高版本的 JDK 中性能已经得到了大幅的提升,很多开发者开始提倡使用 synchronized,性能问题可以不断优化提升,它并不是重载轮子的原因。
大家都知道管程帮助我们解决了多线程资源共享问题,但同时也带来了死锁的风险。
产生死锁的四个必要条件:
1. 互斥条件
一个资源在同一时刻只能被一个线程操作。
2. 占有且等待
线程因为请求资源而阻塞时不会释放已经获取到的资源。
3. 不可强行占有
线程已经获取到的资源,在未释放前不允许被其他线程强行剥夺。
4. 循环等待
线程存在循环等待资源的关系(线程 T1 依次占有资源 A,B;线程 T2 依次占有资源 B,A;这就构成了循环等待资源关系)。
当发生死锁的时候必然上面四个条件都会满足,那么只要我们破坏其中的任何一个条件,我们即可解决死锁问题。首先条件 1 无法破解,因为共享资源必须是互斥的,如果可以多个线程同时操作也没必要加锁了。
使用 synchronized 来破解剩余的三个条件:
1. 占有且等待
synchronized 获取资源时候,只要资源获取不到,线程立即进入阻塞状态,并且不会释放已经获取的资源。那么我们可以调整一下获取共享资源的方式,我们通过一个锁中介,通过中介一次性获取线程需要的所有资源,如果存在单个资源不满足情况,直接阻塞,而不是获取部分资源,这样我们即可解决这个问题,破解该条件。
2. 不可强行占有
synchronized 获取不到资源时候,线程直接阻塞,无法被中断释放资源,因此这个条件 synchronized 无法破解。
3. 循环等待
循环等待是因为线程在竞争 2 个互斥资源时候会以不同的顺序去获取资源,如果我们将线程获取资源的顺序固定下来即可破解这个条件。
综上我们可以知道 synchronized 不能破解“不可强行占有”条件,这就是 JDK 同时提供 Lock 这种管程的实现方式的原因。当然啦,Lock 使用起来也更加的灵活。例如我们有多个共享资源,锁是嵌套方式获取的,如线程需要先获取 A 锁,然后获取 B 锁,然后释放 A 锁,获取 C 锁,接着释放 B 锁,获取 D 锁 等等。这种嵌套获取锁的方式 synchronized 是无法实现的,但是 Lock 却可以帮助我们来解决这个问题。既然我们知道了 JDK 重造管程的原因。
Lock提供的四种进入获取锁的方式:
1. void lock();
这种方式获取不到锁时候线程会进入阻塞状态,和 synchronized 类似。
2. void lockInterruptibly() throws InterruptedException;
这种方式获取不到锁线程同样会进入阻塞,但是它可以接收中断信号,退出阻塞。
3. boolean tryLock();
这种方式不管是否能获取到锁都不会阻塞而是立刻返回获取结果,成功返回 true,失败返回 false。
4. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
这种方式获取不到锁的线程会进入阻塞状态,但是在指定的时间内如果仍未获得锁,则返回 false,否则返回 true。
以上即为问题的答案,小伙伴们对这个答案是否满意呢?要了解更多技术知识请上学步园。