重入锁(ReentrantLock)的引入就是为了解决同步锁存在活跃性的问题。
不同点:
- 可重入锁,同个线程可以对同一个锁多次获取。
- 可重入锁,可以被打断,需创建可打断的锁
lockInterruptibly()
。 - 可重入锁,ReentrantLock默认与同步锁一样是不公平锁,但是可以设置为公平锁,解决同步锁的饥饿,但是意义不大。
- 可以创建条件变量,解决同步锁的虚假唤醒。
- 可以由程序员加锁和释放锁。
- 可以设置锁时长
tryLock()
,带有时长的锁,也是可以被打断的。
相同点:
- 锁都可以被重入。
一、解决同步锁的活跃性
1.1 解决死锁
复现:哲学家就餐问题;synchronized
会一致等待锁。
public class Lock {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("阿基米德", c5, c1).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("苏格拉底", c1, c2).start();
}
static class Philosopher extends Thread {
Chopstick right;
Chopstick left;
public Philosopher(String name, Chopstick right, Chopstick left) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
synchronized (left) {
synchronized (right) {
try {
eat();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
private void eat() throws InterruptedException {
System.out.println(currentThread().getName() + "吃");
}
}
static class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" +
"name='" + name + '\'' +
'}';
}
}
}
结果:死锁
使用
tryLock
解决死锁;结果:停不下来。
public class Lock {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("阿基米德", c5, c1).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("苏格拉底", c1, c2).start();
}
static class Philosopher extends Thread {
Chopstick right;
Chopstick left;
public Philosopher(String name, Chopstick right, Chopstick left) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
if(left.tryLock()){
try{
if(right.tryLock()){
try {
eat();
}finally {
right.unlock();
}
}
}finally {
left.unlock();
}
}
}
}
private void eat() {
System.out.println(currentThread().getName() + "吃");
}
}
static class Chopstick extends ReentrantLock{
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" +
"name='" + name + '\'' +
'}';
}
}
}
1.2解决饥饿
公平锁一般不使用。常见公平锁对象:
new ReentrantLock(true)、new ReentrantReadWriteLock(true)等。
1.3 解决虚假唤醒
public class WaitTest {
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock ROOM = new ReentrantLock();
static Condition waitCigaretteSet = ROOM.newCondition();
static Condition waitTakeoutSet = ROOM.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
ROOM.lock();
try {
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有烟吗? " + hasCigarette);
while(!hasCigarette){
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":没烟,歇会。" );
try {
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有烟吗? " + hasCigarette);
if(hasCigarette){
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有烟,干活。");
}else {
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":接着睡。");
}
}finally {
ROOM.unlock();
}
},"小南").start();
new Thread(()->{
ROOM.lock();
try {
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有饭吗? " + hasTakeout);
while(!hasTakeout){
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":没饭,歇会。" );
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有饭吗? " + hasTakeout);
if(hasTakeout){
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有饭,干活。");
}
}finally {
ROOM.unlock();
}
},"大南").start();
Thread.sleep(1000);
new Thread(()->{
ROOM.lock();
try {
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":饭来。");
hasTakeout = true;
waitTakeoutSet.signal();
}finally {
ROOM.unlock();
}
},"小女").start();
new Thread(()->{
ROOM.lock();
try {
System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":烟来。");
hasCigarette = true;
waitCigaretteSet.signal();
}finally {
ROOM.unlock();
}
},"小女女").start();
}
}
精准唤醒
二、可重入锁原理
java.util .concurrent包中提供了Lock接口,允许自定义一个锁,自定义锁往往利用AbstracQueuedSynchronizer(AQS)
来实现。
Java已经提供一个现成的实现类ReentrantLock
,其也是利用AQS
工具类实现的。
2.1 非公平锁的原理
摘抄:https://blog.csdn.net/qq_37671094/article/details/119760799
2.2 可重入的原理
可重入的加锁 可重入的解锁2.3 可打断的原理
默认不可打断原理:即使被打断,线程仍然会驻留在AQS队列中,等获得锁后继续运行(打断标记被设置true)。
不可打断的原理 可打断的原理2.4 公平锁的原理
公平锁的原理2.5 条件变量的原理
条件变量实现原理三、读写锁原理
摘抄:https://blog.csdn.net/qq_37671094/article/details/119870104
对共享资源是修改还是读取都加锁,会损失性能,那么我们可不可以对锁进行细分,使读-读可并发,由此就引出了该文章,读写锁ReentrantReadWriteLock
3.1 StampedLock
该类自JDK8加入,是为了进一步优化读性能,大的特点是在使用读锁、写锁时配合使用【戳】
StampedLock的核心3.2 Semaphore
摘抄:https://blog.csdn.net/qq_37671094/article/details/119904124
信号量,用来限制能同时访问共享资源的线程上限。
网友评论