目录
多把锁
并发度:
同一个时间节点上,可能与服务端进行交互的用户个数;
如何增强并发度:
首先明确,并发度增强无非就是交互个数在一个时间点上变多——>那么我们可以设置多把锁。目的就是同一时间上交互变多;
例子:
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方法
@Override
public void run() {
while(true){
//2.1一直循环,集齐左右筷子即可触发eat
log.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;
}
@Override
public 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后需要思考1s
sleep(1000);
}
}
/**
* 把筷子当做锁对象,而不用synchronized
* 继承ReentrantLock即可
*/
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
}
公平锁
什么叫不公平: 当你多个线程再等待队列里面进行等待时,获取锁资源,多个竞争一个,而不是按照进入队列时间来竞争,所以是不公平的;
ReentrantLock:可以通过构造方法设置公平还是不公平
公平锁:先进先得;——>解决饥饿问题;——>缺点:会降低并发度
但是我们可以用tryLock()方法来解决;