前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >并发编程常识

并发编程常识

作者头像
小土豆Yuki
发布2020-12-02 11:19:09
2450
发布2020-12-02 11:19:09
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

wait() 方法和 sleep() 方法区别

  1. wait方法会释放对象的锁标志,当调用一个对象的wait方法的时候,会使当前线程暂停执行,并将当前线程放到对象队列池,直到调用notify方法,会把对象队列池的任意一个线程放入锁标记等待池,只有锁标记等待池才可能获取锁,并时刻准备获取锁,如果是notifyAll方法,会把对象等待池所有线程放入到锁标记等待池
  2. sleep需要指定等待时间,他可以让线程在指定时间内暂停执行,但是他并不会释放锁标记,如果是使用synchronizd同步代码块,其他线程也不能共享数据,其他同等级的或者高优先级的线程获取锁执行的机会,等级低的也有可能获取执行的机会
  3. wait只能在同步代码块会同步代码方法使用,而sleep可以在任何地方使用
  4. wait无需要捕获异常,而sleep需要,且sleep是Thread方法,而wait是object类的方法。

notify和notifyAll使用那个唤醒线程

建议使用notifyAll,两个虽然都是唤醒线程,但是还是有区别的,notify是随机唤醒等待线程池的一个线程,而notifyAll会唤醒对象等待队列池的所有线程,看上去使用notify更好一下,但事实上他是有风险的,比如下面例子

有四个资源A,B,C,D,线程1申请到了A和B资源,线程2申请到了C和D,此时线程3想要申请A和B,因此进入等待队列,线程4想要申请C和D,然后线程1释放了A和B资源,如果使用notify唤醒了线程4,但是线程4是想申请C和D,不满足条件就继续等待,而真正想获取资源A和B并没有被唤醒,因此没有特殊要求建议使用notifyAll.

什么是数据竞争

多个线程同时共享同一个数据,且至少与一个线程修改这个数据,比如下面代码

代码语言:javascript
复制
public class Test {
 private long count = 0;
 void add10K() {
 int idx = 0;
 while(idx++ < 10000) {
       count += 1;
     }
   }
 }

在多个线程中调用add10k就会对产生数据竞争,多个线程会修改count这个共性变量,不做安全措施的话,就会因此bug,但是此时如果没有改成下面代码

代码语言:javascript
复制
public class Test {
 private long count = 0;
 synchronized long get(){
 return count;
  }
 synchronized void set(long v){
     count = v;
  } 
 void add10K() {
 int idx = 0;
 while(idx++ < 10000) {
       set(get()+1) //由于get set不具备原子性会导致线程安全问题 
       //get虽然有锁,只能保证多个线程不能同一时刻执行。
       // 但是出现不安全的可能是线程a调用get后线程b调用get,
       // 这时两个get返回的值是一样的。然后都加一后再分别set.
       // 这样两个线程就出现并发问题了。问题在于同时执行get,
       //  而在于get和set是两个方法,这两个方法组合不是原子的,
       // 就可能两个方法中间的时间也有其它线程分别调用,出现并发问题    
    }
  }
}

在add10k中,我使用get和set去访问变量count,对count变量添加互斥性,此时就不存在数据竞争,但是add10k依然是线程不安全的

什么是竞争条件

竞争条件就是线程执行的结果是依赖于线程的执行顺序,比如上面代码两个线程同时执行get,获取到的值是0,执行get()+1的时候获取的值就是1,此时把1更新到内存,最终你获取的结果是1,而不是期望的2.

两个线程同时执行就会是1,如果线程先后执行就会是2,在并发的世界里,线程的执行顺序是不确定的,如果存在竞争条件,就会导致结果的不确定性。

我们也可以按照下面代码理解竞争条件,程序的执行以来某个状态

代码语言:javascript
复制
if (状态变量 满足 执行条件) {
  执行操作
}

当某个线程发现状态变量满足条件,执行操作,可就在线程执行的时候,其他线程修改了状态变量,就会不在满足条件了,

什么是死锁,活锁,饥饿

死锁就是多个线程互相等待,而且会一直等待下去,也就是指阻塞.

活锁就是虽然线程没有阻塞,但是也不会执行下去,在显示生活中,一个人A右手边进门,一个人B从左手边出门,两个人就会碰到,然后两个人谦让,A从左手边进门,B从右手边进门,然后又碰上了,当然人是会交流的,交流之后,就可以解决,但是编程的世界如何解决呢,当然也是非常简单的,只要每一个线程随机时间后在去执行,这样就可以大大减少碰到的概率。

饥饿是线程无法获取到资源而无法执行下去,如果线程的优先级不均,在CPU繁忙下,优先级低的线程无法获取到资源就会导致饥饿,还有就是持有锁时间过长,也会导致饥饿的发生,饥饿也是有策略解决的,可以均匀分配资源,减少持有锁的时间,还有就是资源充足.

什么是管程

管程是指对管理共享变量以及共享变量的操作,让他们支持并发,管程应为名Monitor,直译就是监视器,意译就是管程。

我们要注意管程和信号量是等价的,等价是指,可以用管程实现信号量,也可以用信号量实现管程。

管程是如何管理的呢?

管程是有三种模式,分别是:Hasen 模型、Hoare 模型和 MESA 模型,而java的管程参考实现使用的MESA

在并发编程中,有两大核心内容,同步和互斥,互斥就是指同一时刻只有一个线程执行,同步是指线程之间的协作和通信,

利用管程可以很简单的实现互斥,就是把共享变量和共享操作封装起来,对外提供安全的操作,

正如上图一样,我们使用管程把共享变量和对应的入队enq和出队dep封装起来,线程A和线程B要进行队列的操作就要使用管程提供的enq和dep实现,enq和dep保持了互斥,同一时刻只有一个线程进入管程.

管程实现同步就比较复杂了,我们先来看一下MEAS管程模型的图如下显示

上图外面的黑框就是实现封装的意思,最上面只有一个入口,同一时刻只会有一个线程进入,当多个线程想进入的时候,其余的线程会进入入口等待队列,而管程内部有条件变量和条件变量等待队列,每一个条件变量只有一个条件等待队列,

而条件变量和条件变量等待队列就是实现同步的,这里我要注意一个问题,就是用管程实现阻塞队列和管程内的等待队列是不一样的东西,我们举个例子,一个线程1对管程实现的阻塞队列进行出队的操作,出队的前提条件就是阻塞队列不能为空,而这个前提条件就是管程里面的条件变量,当从阻塞队列出队的时候,发现阻塞队列为空,怎么办呢,此时就会进入等待,而这个等待就是管程里面的条件变量等待队列,然后又有一个线程2要对管程实现的阻塞队列进行入队操作,如果入队成功之后,此时阻塞队列不为空的条件,对于线程1就已经满足了,线程2就会通知线程1,线程1就会从条件变量等待队列出队,但是并不会直接执行,而是进入管程入口的等待队列,

使用管程写一个线程安全的队列

代码语言:javascript
复制
public class BlockedQueue<T>{
 final Lock lock =
 new ReentrantLock();
 // 条件变量:队列不满  
 final Condition notFull =
     lock.newCondition();
 // 条件变量:队列不空  
 final Condition notEmpty =
    lock.newCondition();

 // 入队
 void enq(T x) {
    lock.lock();
 try {
  while (队列已满){
 // 等待队列不满 
        notFull.await();
      }  
 // 省略入队操作...
 //入队后,通知可出队
       notEmpty.signal();
     }finally {
       lock.unlock();
     }
   }
 // 出队
 void deq(){
    lock.lock();
 try {
  while (队列已空){
 // 等待队列不空
        notEmpty.await();
      }
 // 省略出队操作...
 //出队后,通知可入队
      notFull.signal();
    }finally {
      lock.unlock();
    }  
  }
}
  1. 对于阻塞队列入队操作,如果阻塞队列已满,就需要等待直到阻塞队列不满,这使用notFull.await.
  2. 对于阻塞队列出队操作,如果阻塞队列为空,就需要等待阻塞对垒不为空,使用notEmpty.await
  3. 当入队成功,阻塞队列就不为空了,此时就要通知条件变量:notEmpry的等待队列
  4. 当出队成功,阻塞队列不满,此时就要通知条件变量:notFull的等待队列

notify在什么场景使用

上面我们讲过,尽可能使用notifyAll,但是什么时候使用notify呢,需要满足下面三个条件

  1. 所有等待线程拥有相同的等待条件
  2. 所有的等待线程唤醒后,执行相同的操作
  3. 只需要唤醒一个线程

如果对您有一丝丝帮助,麻烦点个关注,也欢迎转发,谢谢

扫码关注

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档