多线程学习---线程同步与等待唤醒机制

线程同步

前面我们简单的理解了一下多线程,理解多线程存在的意义。这里我们必须要明确一点,多线程有其优点,必有其问题,这个问题就是我们要解决的。假设两个线程访问一个共享资源,要对这个共享资源进行修改,若他们同时对这个数据修改,不就是出现问题了嘛,共享资源没有得到及时的更新,也无法预测其之后的结果,这是很大的安全问题,所以也就引出了解决办法,线程同步。
线程同步的思想就是,在涉及共享资源的代码段上,一段时间内只能有一个线程执行,其他线程等待,只有等当前执行的线程执行完了,其他线程才能够访问共享资源。
这样就避免了多个线程同时访问或修改共享资源的问题。具体实现同步有三种方法:

  • 同步代码块
  • 同步方法
  • lock锁

这里就不赘述各个方法是什么怎么用这种事儿了。
三者其实都用到锁这个概念,这里特别是同步代码块与lock锁用到了锁对象,多个线程都以某一个对象为锁,谁持有这个锁,就拥有了同步代码快的执行权,当执行完毕之后,会把锁对象释放,让剩下的线程们去抢锁。就像是将军的虎符,虎符只是一个标志,它是用玉做的还是铁做的我们不关心,我们关心的是这个虎符要是唯一的就行,我们在乎它的意义。同一时间只有一个人能持有虎符,一旦持有虎符就能调动军队,等到这个人任务完成,任期一满,交出虎符,让皇帝来决定虎符给谁用。

同步存在的问题

在同步中,我们知道,只有一个线程同时在访问共享资源,这解决了多线程的安全问题,但同时也带来一个问题:多个线程谁先访问共享资源我们是不能确定的,假如线程之间需要有先后顺序的话,那就会卡在同步代码块那里。
比如操作系统中老生常谈的消费者生产者问题,生产者生产了商品,消费者才能购买商品。那假如程序一开始消费者先拿到了锁,那么它也没有商品可以消费(还没生产呢),它又必须消费,那么久一直卡着,也执行不了剩余代码,而生产者因为锁被消费者占着,也不能访问商品进行生产,所以一直在同步代码块之外候着,那完蛋了,消费者等生产者生产才能消费才能释放锁,生产者等消费者释放锁才能生产,貌似就形成了“死锁”。
这个问题是很严重的,所以也就引出了等待唤醒机制,来解决这一问题。

等待唤醒机制

我们需要明确几个状态:
阻塞态:可以运行,但暂时没有cpu资源
运行态:正在运行
等待状态:没有锁对象,暂时等待

明确几个方法:
wait():进入等待状态,并立即释放锁对象
notify():继续执行完同步代码块,之后释放锁对象

具体的状态转移如下图所示
多线程学习---线程同步与等待唤醒机制_第1张图片
根据这张图片
我们以生产者消费者为例。
生产者消费者线程创建。假设消费者先获得锁对象,进入同步代码块,那么此时生产者在同步代码块外等待。消费者继续执行,发现商品数为0,则执行wait()方法,释放锁对象,并且自己进入等待状态,等待被唤醒,此时消费者的代码执行到wait()位置,停住不动,被锁锁住,直到被唤醒。因为锁对象被释放,所以生产者获得锁对象,进入同步代码块,发现商品数为0,那么它就开始生产商品,商品数+1,之后它执行notify()方法,一直执行完同步代码块之后,释放锁对象。消费者被notify(),并等到获得锁对象之后继续执行,消费掉刚刚生产的商品,之后执行notify()提醒生产者生产商品。如此往复,这就是等待唤醒机制的大概流程。
其中,需要注意:

  1. wait()之后是立即释放锁对象的,而notify是执行完剩余同步代码块之后才释放锁对象的,(如果立即释放,自己也被卡住了)
  2. 正在运行的线程执行notify后,会把等待队列中的一个唤醒,其余的不唤醒;这个被唤醒的也不能进入运行态,而是进入阻塞态,因为它并没有锁对象,它得和其他阻塞态的线程一起竞争锁对象
  3. 这里的notify的作用其实是将一个等待状态的线程变成阻塞态
  4. wait()方法和notify()方法必须由一个锁对象调用,这里的等待唤醒都是在几个线程同步的基础上实现的,不同的同步问题不相互干扰,不过要注意锁对象的唯一;这两个方法都是属于object类的方法,所以锁对象可以是任意类

以上便是对等待唤醒机制的理解。等待唤醒机制解决了共享资源访问的问题(本身就是在同步的基础上实现),也解决了共享资源访问先后顺序问题(或者说共享资源访问的条件问题),实现了线程间的通信(协同工作)。

你可能感兴趣的:(java基础学习)