目录
多把锁
活跃性
定位死锁
例题:哲学家问题
活锁
饥饿
ReentrentLock锁
用法
打断锁
tryLock()设置超时方法
哲学家问题优化——ReentrantLock解决死锁
公平锁
多把锁
并发度:
同一个时间节点上,可能与服务端进行交互的用户个数;
如何增强并发度:
首先明确,并发度增强无非就是交互个数在一个时间点上变多——>那么我们可以设置多把锁。目的就是同一时间上交互变多;
例子:
package com.example.juc.Multi;import lombok.extern.slf4j.Slf4j;/*** @author diao 2022/4/10*/
@Slf4j(topic = "c.TestMultiLock")
public class TestMultiLock {public static void main(String[] args) {BigRoom room = new BigRoom();/***room中的sleep与study方法锁资源是不一致的* 有利于增强并发度,不过容易发生死锁*///1.小明线程睡觉new Thread(()->{try {room.sleep();} catch (InterruptedException e) {e.printStackTrace();}},"小明").start();//2.小花线程学习new Thread(()->{try {room.study();} catch (InterruptedException e) {e.printStackTrace();}},"小花").start();}
}
@Slf4j(topic = "c.BigRoom")
class BigRoom{//1.提供两种房间,一个睡觉的一个学习的,两个业务分开private final Object studyRoom=new Object();private final Object bedRoom=new Object();public void sleep() throws InterruptedException {synchronized (bedRoom){log.debug("睡2秒");Thread.sleep(2000);}}public void study() throws InterruptedException {synchronized (studyRoom){log.debug("学习1s");Thread.sleep(1000);}}
}
活跃性
死锁:相互调用对方的同步资源,但是锁资源又都没释放
例子:
package com.example.juc.Multi;import lombok.extern.slf4j.Slf4j;/*** @author diao 2022/4/10*/
@Slf4j(topic = "c.DeadLock")
public class DeadLock {public static void main(String[] args) {testDead();}public static void testDead(){//1.锁资源Object A=new Object();Object B=new Object();Thread t1=new Thread(()->{synchronized (A){log.debug("即将lock B");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (B){log.debug("lock B完成");}}},"t1");t1.start();Thread t2=new Thread(()->{synchronized (B){log.debug("即将lock A");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (A){log.debug("log A完成");}}});t2.start();}
}
定位死锁
jps:列出执行进程
jstack 进程号:找到信息
或者用jconsle(可视化)
例题:哲学家问题
一方等待另一方释放资源——>而另一方也在等待其他线程释放资源;
package com.example.juc.Multi;import lombok.extern.slf4j.Slf4j;/*** @author diao 2022/4/10*/@Slf4j(topic = "c.TestDeadLock2")
public class TestDeadLock2 {public static void main(String[] args) {//0.创建5个筷子对象Chopstick chopstick1 = new Chopstick("1");Chopstick chopstick2 = new Chopstick("2");Chopstick chopstick3 = new Chopstick("3");Chopstick chopstick4 = new Chopstick("4");Chopstick chopstick5 = new Chopstick("5");new People("小明",chopstick1,chopstick2).start();new People("小花",chopstick2,chopstick3).start();new People("小强",chopstick3,chopstick4).start();new People("小黑",chopstick4,chopstick5).start();new People("小安",chopstick5,chopstick1).start();}}
@Slf4j(topic = "c.People")
class People extends Thread{//1.两个锁资源:左边筷子和右边筷子Chopstick left;Chopstick right;final String name;public People(String name,Chopstick left, Chopstick right) {this.left = left;this.right = right;this.name=name;}//2.重写Thread的run方法@Overridepublic void run() {while(true){//2.1一直循环,集齐左右筷子即可触发eatlog.debug("{}拿筷子",name);synchronized (left){log.debug("{}拿左筷子",name);synchronized (right){log.debug("{}拿右筷子",name);try {eat();} catch (InterruptedException e) {e.printStackTrace();}}}}}private static void eat() throws InterruptedException {log.debug("eating...");//当前线程吃了后,其他线程竞争拿筷子sleep(1000);}
}class Chopstick{String name;public Chopstick(String name) {this.name = name;}
}
当每个人都拿一根筷子的时候就放下,deadLock
活锁
简单来说,就是一个造,一个解决——>导致一直解决不了,比如说20个数,一个线程减,一个线程加,就跟永动机一样;
package com.example.juc.Multi;import lombok.extern.slf4j.Slf4j;import static java.lang.Thread.sleep;/*** @author diao 2022/4/10*/
@Slf4j(topic = "c.LiveLockTest")
public class LiveLockTest {//1.锁资源static volatile int count=10;static final Object lock=new Object();public static void main(String[] args) {//2.加减线程new Thread(()->{//2.1当减到count<=0结束while(count>0){try {sleep(20);} catch (InterruptedException e) {e.printStackTrace();}count--;log.debug("当前count{}",count);}},"t1").start();new Thread(()->{while(count<20){try {sleep(20);} catch (InterruptedException e) {e.printStackTrace();}count++;log.debug("当前count{}",count);}},"t2").start();}
}
饥饿
我们可以按照顺序加锁,而不是一个线程先获取A,另一个获取B,然后第一个线程再获取B;
我们这个顺序加锁就可以直接先获取AB,就不会出现死锁了;
ReentrentLock锁
可中断:持有锁的线程一直不释放资源的情况下,处于阻塞状态的等待线程可以放弃等待;
支持多个条件变量:意思就是支持多个waitSet(多个条件)——>多个休息室
回顾一下:进入休息室的条件就是拥有锁资源,但是又放弃了,所以得在获取锁资源的条件下——>ReentrentLock特别在于不同条件会放在不同的waitSet中——>有利于减少唤醒次数;
共同点:支持可重入;
用法
package com.example.juc.ReengtrantLock;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;/*** @author diao 2022/4/10*/
@Slf4j(topic = "c.ReentrantLock")
public class ReentrantLockTest {//1.锁资源private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {//2.上锁lock.lock();try {log.debug("进入主方法");m1();} finally {lock.unlock();}}public static void m1() {lock.lock();try {log.debug("进入m1方法");m2();} finally {lock.unlock();}}public static void m2(){lock.lock();try {log.debug("进入m2方法");} finally {lock.unlock();}}
}
打断锁
lock.lockInterruptibly():当获取不到锁资源,就暂停往下执行
t1.interrupt():打断t1线程,结束等待的意思,如果t1线程获取不到锁资源,就会直接退出等待,抛出异常;——>停止无限期的等待,比如死锁
这两个方法时配合起来用的;
tryLock()设置超时方法
package com.example.juc.ReengtrantLock;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;/*** @author diao 2022/4/10*/
@Slf4j(topic = "c.Test21")
public class Test21 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("尝试获得锁");try {//会等待指定时间,如果在指定时间得到锁资源,则向下执行if(!lock.tryLock(5, TimeUnit.SECONDS)){log.debug("获取不到锁");return;}} catch (InterruptedException e) {//因为lock.tryLock()也是可以被打断的e.printStackTrace();log.debug("获取不到锁");return;}try {log.debug("获得到锁");} finally {lock.unlock();}}, "t1");//主线程先获取锁lock.lock();log.debug("主线程获取到锁");t1.start();}
}
可以发现:
锁对象.tryLock()——>可以设置锁的等待时间,如果在指定时间内都没有得到锁资源就会不往下执行了;
作用:避免无休止的等待,类似于保护性暂停模式;
哲学家问题优化——ReentrantLock解决死锁
思路:当一个人只有一根筷子,而另一跟筷子在别人手里时,我们会释放掉当前手中的筷子谦让给别人——>目的:减少饥饿,让每个人都吃得上饭
package com.example.juc.ReengtrantLock;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;/*** @author diao 2022/4/10*/
@Slf4j(topic = "c.TestDeadLock3")
public class ReentrantLockTest3 {public static void main(String[] args) {//0.创建5个筷子对象Chopstick chopstick1 = new Chopstick("1");Chopstick chopstick2 = new Chopstick("2");Chopstick chopstick3 = new Chopstick("3");Chopstick chopstick4 = new Chopstick("4");Chopstick chopstick5 = new Chopstick("5");new People("小明",chopstick1,chopstick2).start();new People("小花",chopstick2,chopstick3).start();new People("小强",chopstick3,chopstick4).start();new People("小黑",chopstick4,chopstick5).start();
// new People("小安",chopstick5,chopstick1).start();//将锁对象换位置,但是会导致某些线程饥饿,一直获取不到锁资源new People("小安",chopstick1,chopstick5).start();}
}/*** 线程People*/
@Slf4j(topic = "c.People")
class People extends Thread{final String name;Chopstick left;Chopstick right;public People(String name, Chopstick left, Chopstick right) {this.name = name;this.left = left;this.right = right;}@Overridepublic void run() {while(true){//1.尝试获得左筷子if(left.tryLock()){try {//2.尝试获得右手筷子if(right.tryLock()){try {eat();} catch (InterruptedException e) {e.printStackTrace();}finally {//3.有得话eat完就给放了right.unlock();}}}finally {//4.第二个if判断失败,释放左手筷子left.unlock();}}}}public static void eat() throws InterruptedException {log.debug("eating...");//线程eat后需要思考1ssleep(1000);}
}/*** 把筷子当做锁对象,而不用synchronized* 继承ReentrantLock即可*/
class Chopstick extends ReentrantLock {String name;public Chopstick(String name) {this.name = name;}
}
公平锁
什么叫不公平: 当你多个线程再等待队列里面进行等待时,获取锁资源,多个竞争一个,而不是按照进入队列时间来竞争,所以是不公平的;
ReentrantLock:可以通过构造方法设置公平还是不公平
公平锁:先进先得;——>解决饥饿问题;——>缺点:会降低并发度
但是我们可以用tryLock()方法来解决;