栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程——条件对象

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程——条件对象

文章目录
  • 条件对象
    • 1.2.1定义
    • 1.2.2例子
    • 1.2.3实现

条件对象

之前提到了,如果线程持有一个临界资源,那么在线程操作临界资源的过程中对待其他的线程是互斥的,也就是说在某一时刻只有持有临界资源的线程才能访问,读写临界资源, 而其他的线程是不可以的,这样就保证了一定次序从而避免讹误的产生。

那么很显然就有一个问题,对于一个锁来说,几个线程都想持有它,这几个线程都是公平的,就看线程调度器选择哪个,但是如果一个线程持有了一个锁,但是并不是说运行的条件就是一定的满足,有可能会产生一直困死在这的问题,因为这个线程持有锁但是没有能力所以导致程序也完全崩溃了。

那么如何解决这个问题?

理所当然的就能想到,既然这个线程没有能力去执行,为什么不让他先释放掉资源,别占着* * 不* *,先让其他有能力的线程去执行,又不是欺负它不让他执行,等条件合适的时候就叫醒他,让他执行。

1.2.1定义

当线程进入临界区,却发现某一条件满足之后它才能执行,这时就可以使用一个条件对象来管理那些已经获得了一个锁但是却不能运行的线程。

1.2.2例子

在之前的银行模拟程序中,我们理所当然的写出了一些代码:

            if (account[from] < amount){
                
                return;
            }

这个代码看着没有问题,是因为代码在串行的思维上是没有问题的,这个时候确实需要去检查当前账户的额度是否支持转账,但是存在一个问题,因为我们当前的程序是多线程的,如果一个线程通过了额度检查,但是却被中断,在他进行转账账户额度增减的操作前,线程被中断,其他的线程修改了额度,进而导致,第一个线程因为额度不满足转账需求,系统出错了。

显然这样的问题是不行的,而此时我们也很直接的可以考虑到用一个锁来保证访问账户和修改账户的额度之间的操作的原子性就能保证不会因为中断出这样的问题。

但是我的需求并不是这样的,我的需求是:如果当前的线程操作账户的额度并不足以支持装置,所以线程要一直等待直到这个条件满足的情况再去转账。那么此时应该如何去处理呢?

分析一下需求:

  • 1)本线程要等待条件满足
  • 2) 操作的账户属于临界资源,要给其他线程机会可以修改这个临界资源,从而能满足条件
  • 3)条件满足的时候,线程要能再去转账
1.2.3实现

通过是用条件对象来实现这个操作

常用的方法:

  • java.util.concurrent.locks.Lock
    • Condition newCondition()
      • 返回一个于该锁相关的条件对象
  • java.util.concurrent.locks.Condition
    • void await()
      • 将线程放到条件对象的等待集中
    • void signalAll()
      • 解除该条件对象等待集中的所有线程的阻塞状态
    • void signal()
      • 从该条件的等待集中随机选一个线程,解除器阻塞状态
    
    private Lock bankLock = new ReentrantLock();

    
    private Condition sufficientFunds;

    public void transfer(int from,int to,double amount){
        bankLock.lock();
        try {
            
            while (account[from] < amount){
                
                sufficientFunds.await();
            }
            System.out.println(Thread.currentThread());
            account[from] -= amount;
            
            System.out.printf(" %10.2f from %d to %d",amount,from,to);
            account[to] += amount;
            System.out.printf(" Total Balance: %10.2f%n",getTotalBalance());

            
            sufficientFunds.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bankLock.unlock();
        }
    }
    

重点:什么时候调用await?

显然应当在条件不满足的时候调用await?但是根据条件对象的使用,此时的线程在被唤醒后是需要再去检查是否满足条件的,并不是说唤醒之后就能工作下去了。

所以使用的格式应当是:

while(!(ok to proceed)){
	condition.await();
}

调用await 后,这个线程就进入了阻塞的状态,并且释放了锁,它无法自己激活自己,而是把希望给到了其他的线程,那么需要注意的就是如果没有线程可以激活它,那么它就陷入了死锁的状态。

重点:核心在于什么时候调用signalAll方法?

显然,如果调用signalAll方法每个线程此时都会去检查余额是否满足条件,如果没有满足条件就会再进入条件对象的等待集中,而满足条件则就会执行下去。

所以signalAll方法的调用必须在此时发生的事件可能会使得条件满足。

也就是上面完成了转账操作后,此时每个线程并不能确定是不是我的条件满足了,但是唤醒其他线程的线程,只是告诉他们,有这个可能,具体谁的条件满足了,调度器调度哪个线程都是不确定的。

重点:signal 和signalAll 的区别

signal 会随机的激活等待集中的某个线程的阻塞状态,这比接触所有的线程的阻塞状态显然更加的有效,但是也存在危险,如果随机选择的线程发现自己仍然不能运行,那么它仍旧会被阻塞,但是此时如果没有其他的线程再次调用signal 方法,那么系统就死锁了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/658356.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号