并发编程3

本文介绍了Java并发编程中的管程概念,包括多把不相干的锁如何提高并发度以及可能出现的死锁问题。接着讨论了ReentrantLock的特点,如可中断、可设置超时、公平锁支持以及条件变量的使用。还提到了同步模式的顺序控制,如固定运行顺序和交替输出的实现方式。文章强调了不同锁策略对并发性能和线程安全的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并发编程-共享模型之管程2

4.11多把锁

多把不相干的锁

一间大屋子有2个功能:睡觉、学习、互不相干

现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低

解决方法是准备多个房间(多个锁对象)

实现:

public class Test7 {
    public static void main(String[] args) {
        Bigroom bigroom = new Bigroom();
        new Thread(()->{
            try {
                bigroom.study();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"小南").start();
        new Thread(()->{
            try {
                bigroom.sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"小女").start();

    }
}
@Slf4j
class Bigroom{
    private final Object studyRoom= new Object();
    private final Object bedRoom= new Object();
    public void sleep() throws InterruptedException {
        synchronized (bedRoom){
            log.info("sleep 2小时");
            Thread.sleep(2);
        }
    }
    public void study() throws InterruptedException {
        synchronized (studyRoom){
            log.info(" study 1小时");
            Thread.sleep(1);
        }
    }

}

将锁的粒度细分

  • 好处,是可以增强并发度

  • 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁

4.12活跃性

死锁

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁

t1线程获得 A对象 锁,接下来想获取 B对象 的锁

t2线程获得 B对象 锁,接下来想获取 A对象 的锁

@Slf4j
public class DeadLock {
    public static void main(String[] args) {
        test1();
    }
    private static void test1(){
        Object A = new Object();
        Object B = new Object();
       Thread t1= new Thread(()->{
            synchronized (A){
                log.info("lock A");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                    log.info("lock B");
                    log.info("操作。。。");
                }
            }
        },"t1");
        Thread t2= new Thread(()->{
            synchronized (B){
                log.info("lock B");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){
                    log.info("lock A");
                    log.info("操作。。。");
                }
            }
        },"t2");
        t1.start();
        t2.start();

    }
}

定位死锁

检测死锁可以使用jconsole工具,或者使用jps定位进程id,再用jstack定位死锁

使用jps

使用jconsole工具

哲学家就餐问题

活锁

活锁出现在2个线程互相改变对方的结束条件,最后谁也无法结束,例如

饥饿

顺序加锁的解决方案可能导致饥饿(部分线程的到锁的机会太低)

4.13ReentrantLock

相对于synchronized它具备如下特点

  • 可中断

  • 可以设置超时时间

  • 可以设置公平锁(防止饥饿)

  • 支持多个条件变量(monitor中的waitset,synchronized只支持一个,ReentrantLock可以根据条件到不同的waitset中等待)

与synchronized一样,都支持可重入 (线程对同一个对象反复加锁)

基本语法

//获取锁
reentrantLock.lock();
try{
	//临界区
}finally{
	//释放锁
	reentrantLock.unlock();
}

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这本锁的拥有者,因为有权力再次获取这把锁

如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

@Slf4j
public class test8 {
    private static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try {
            log.info("main..");
            m1();
        } finally {
            lock.unlock();
        }

    }
    public static void m1(){
        lock.lock();
        try {
            log.info("m1..");
            m2();
        } finally {
            lock.unlock();
        }
    }
    public static void m2(){
        lock.lock();
        try {
            log.info("m2..");
        } finally {
            lock.unlock();
        }
    }
}

可打断

ReentrantLock中有可打断的机制(方法)

ReentrantLock.lockInterruptibly()可以被打断

ReentrantLock.lock()不可以被打断

@Slf4j
public class test9 {
    private static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                //如果没有竞争那么此方法就会获取lock对象锁
                //如果有竞争就会进入阻塞队列,可以被其他线程用interrupt方法打断
                log.info("尝试获取锁");
                lock.lockInterruptibly();// lock.lock()方法不能被打断
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.info("没有获取锁");
                return;
            }
            try {
                log.info("获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        t1.start();
        Thread.sleep(1);
        log.info("主线程打断t1线程");
        t1.interrupt();
    }
}

锁超时

@Slf4j
public class test10 {
    private static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            log.info("尝试获得锁");
            try {
                //获取锁,尝试等待1s,获取不到,lock.tryLock()返回false
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.info("没有获得锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                //tryLock方法可以被打断
                return;
            }
            try {
                log.info("获得到锁");
            } finally {
                lock.unlock();
            }
        },"t1");
        lock.lock();
        t1.start();
        Thread.sleep(1);
        log.info("主线程释放锁");
        lock.unlock();
    }
}

公平锁

synchronized是不公平的,不会按照阻塞队列的顺序来获取,而是一起争抢锁

ReentrantLock默认是不公平的,但是可以通过构造方法,传入true,来保证公平性,公平锁一般没有必要,会降低并发度,后面有原理分析

条件变量

synchronized中也有条件变量,就是讲解原理时哪个waitSet休息室,当条件不满足时进入waitSet等待

ReentrantLock的条件变量比synchronized的强大之处在于,它是支持多个条件变量的,区别:

  • synchronized是那些不满足条件的线程都在一间休息室等待

  • 而ReentrantLock支持多间休息室,唤醒时按休息室唤醒,不会影响其他的休息室

使用流程

  • await前需要获得锁

  • await执行后,会释放锁,进入conditionObject等待

  • await的线程被唤醒(或打断、或超时)取重新竞争lock锁

  • 竞争lock锁成功后,从await后继续执行

@Slf4j
public class test11 {
    private static boolean yan=false;
    private static boolean waiMai=false;
    private static ReentrantLock lock=new ReentrantLock();
    //等待烟的休息室
    private static Condition waitYan=lock.newCondition();
    //等待外卖的休息室
    private static Condition waitWaiMai=lock.newCondition();
    public static void main(String[] args) throws InterruptedException {

        new Thread(()->{
            log.info("有烟没,{}",yan);
            lock.lock();
            try {
                while (!yan){
                    log.info("没有烟,休息一下");
                    try {
                        waitYan.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("有烟,可以开始干活");
            } finally {
                lock.unlock();
            }
        },"小南").start();

        new Thread(()->{
            log.info("外卖送达没,{}",waiMai);
            lock.lock();
            try {
                while (!waiMai){
                    log.info("没有外卖,休息一下");
                    try {
                        waitWaiMai.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("有外卖,可以开始干活");
            } finally {
                lock.unlock();
            }
        },"小女").start();
        Thread.sleep(1);

        new Thread(()->{
            lock.lock();
            try{
                //改变条件变量再叫醒
                waiMai=true;
                waitWaiMai.signal();
            }finally {
                lock.unlock();
            }
        },"送外卖线程").start();


        new Thread(()->{
            lock.lock();
            try{
                //改变条件变量再叫醒
                yan=true;
                waitYan.signal();
            }finally {
                lock.unlock();
            }
        },"送烟线程").start();
    }
}

同步模式之顺序控制

固定运行顺序

先打印2后打印1

用synchronized

@Slf4j
public class test12 {
    private static boolean flag=false;
    private static Object obj= new Object();
    public static void main(String[] args) {
        new Thread(()->{
            synchronized (obj){
                while (!flag){
                    log.info("进入等待");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("打印1");
            }
        },"t1").start();
        
        new Thread(()->{
            synchronized (obj){
                log.info("打印2");
                flag=true;
                obj.notifyAll();
            }
        },"t2").start();
    }
}

用ReentrantLock

@Slf4j
public class test13 {
    private static boolean flag=false;
    private static ReentrantLock lock=new ReentrantLock();
    private static Condition condition1=lock.newCondition();
    public static void main(String[] args) {
        new Thread(()->{
                lock.lock();
            try {
                while (!flag){
                    log.info("进入等待");
                    try {
                        condition1.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("打印1");
            } finally {
                lock.unlock();
            }

        },"t1").start();

        new Thread(()->{
            lock.lock();
            try {
                log.info("打印2");
                flag=true;
                condition1.signalAll();
            } finally {
                lock.unlock();
            }

        },"t2").start();
    }
}

用park方法和unpark方法

@Slf4j
public class test14 {
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            LockSupport.park();
            log.info("打印1");
        },"t1");
        t1.start();
        new Thread(()->{
            LockSupport.unpark(t1);
            log.info("打印2");
        },"t2").start();
    }
}

交替输出

线程1输出a 5次,线程2输出b 5次,线程3输出c 5次。现在要求输出abc abc abc abc abc

用synchronized

@Slf4j
public class test15 {

    public static void main(String[] args) {
        WaitNotify wn = new WaitNotify(1, 5);
        new Thread(() -> {
            wn.print("a", 1, 2);
        }, "a").start();
        
        new Thread(() -> {
            wn.print("b", 2, 3);
        }, "b").start();
        
        new Thread(() -> {
            wn.print("c", 3, 1);
        }, "c").start();
    }
}
/**
 * 输出内容    等待标记    下一个标记
 * a          1           2
 * b          2           3
 * c          3           1
 */
class WaitNotify {
    public void print(String str, int waitFlag, int nextFlag)  {
        for (int i = 0; i < loopNum; i++) {
            synchronized (this) {
                while (flag != waitFlag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
    //等待标记
    private int flag;
    //循环次数
    private int loopNum;

    public WaitNotify(int flag, int loopNum) {
        this.flag = flag;
        this.loopNum = loopNum;
    }
}

用ReentrantLock

用park、unpark

本章小结

1.monitor是给synchronized配合使用,是基于jvm实现,底层是c++

2.ReentrantLock是java层面的monitor

3.保护性暂停模式,是2个线程传递结果的

4.生产者消费者模式,结果产生者和结果消费者不是一一对应的关系

5.顺序控制,控制线程执行的顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闫凯文+1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值