多锁
只有一个对象锁的话,程序的并发度很低(加synchornized 串行执行程序了)
我们可以准备多个对象锁,将锁的粒度细分
-
好处 增强并发度
-
坏处 如果一个线程需要同时获得多把锁,就容易发生死锁
1 死锁
一个线程需要同时获取多把锁,这时容易发生死锁
t1 线程 获得A对象锁,接下来想获取B对象的锁
t2 线程获得B对象锁,接下来向获取A对象锁
// 线程1 需要获取多把锁
new Thread(()->{
synchronized (a){
log.debug("获得了A锁");
synchronized (b){
log.debug("获得了B锁");
}
}
}).start();
// 线程2 需要获取多把锁
new Thread(()->{
synchronized (b){
log.debug("获得了b锁");
synchronized (a){
log.debug("获得了a锁");
}
}
}).start();
}
2 定位死锁
可以使用 jconsole 或者使用 jps 定位进程id,再用jstack定位死锁
jconsole中有检测死锁功能
使用jps查看进程id,jstack查看信息
在展示的信息中,它会给你列出 出现死锁的线程
3 哲学家就餐问题
是一个会导致死锁问题的经典场景,当哲学家们各拿起一个筷子,就会陷入死锁阶段:可以将筷子认为锁资源,哲学家认为是线程
解决方案:使用ReentrantLock的超时锁
当在规定时间内获取不到锁时,放弃竞争,并主动释放自身的锁
筷子类
//就餐所使用的筷子
class Chopstick2 extends ReentrantLock {
String name;
public Chopstick2(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
哲学家类
//哲学家
class Philosopher2 extends Thread{
Chopstick2 left;
Chopstick2 right;
public Philosopher2(String name,Chopstick2 left, Chopstick2 right) {
super(name);
this.left = left;
this.right = right;
}
private void eat(){
log.debug("eat ting");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
// 尝试获取左手筷子
if(left.tryLock()){
//加锁成功
try{
//临界区
//尝试获取右手筷子
if(right.tryLock()){
try{
eat();
}finally {
right.unlock();
}
}
}finally {
// 释放自身的锁
left.unlock();
}
}
}
}
}
调用
Chopstick2 c1 = new Chopstick2("1");
Chopstick2 c2 = new Chopstick2("2");
Chopstick2 c3 = new Chopstick2("3");
Chopstick2 c4 = new Chopstick2("4");
Chopstick2 c5 = new Chopstick2("5");
new Philosopher2("苏格拉底", c1, c2).start();
new Philosopher2("柏拉图", c2, c3).start();
new Philosopher2("亚里士多德", c3, c4).start();
new Philosopher2("赫拉克利特", c4, c5).start();
new Philosopher2("阿基米德", c5, c1).start();