一、常规的线程通信
参考博文:https://www.cnblogs.com/Wenxu/p/7979023.html
1、为什么要线程通信?
- 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
- 当然如果我们没有使用线程通信来使用多线程共同操作同一份数据的话,虽然可以实现,但是在很大程度会造成多线程之间对同一共享变量的争夺,那样的话势必为造成很多错误和损失!
- 所以,我们才引出了线程之间的通信,多线程之间通信能够避免对同一共享变量的争夺。
2、什么是线程通信?
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。于是我们引出了等待唤醒机制:就是在一个线程进行了规定操作后,就进入等待状态(wait), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify);
(1)wait()方法:
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
(2)notif()方法:
notify() 方法会随机唤醒一个等待当前对象的锁的线程。随机唤醒在此对象监视器上等待的单个线程。
(3)notifyAll()方法:
唤醒在此对象监视器上等待的所有线程。
(4)以上线程通信的三个方法,都在Object 类中定义
因为该方法在操作同步中的线程的时候都必须标识其所操作线程所持有的锁(被该锁的对象调用),而只有同一个对象监视器下(同一个锁下)的被等待线程,可以被持有该锁的线程唤醒(无法唤醒不同锁上的线程),所以,等待和唤醒的必须是用一个对象监视器下(同一个锁上)的线程。而锁时任意已经确定的对象,能被任意对象调用的方法应当定义在Object类中。
监视器(锁):同一对象的监视器(同一把锁上)的线程,一次只能执行一个,就是拥有监视器所有权(持有锁)的哪一个线程。
3、生产者和消费者问题
问题:
- 生产者生产面包;消费者消费面包。
- 面包放在一个面包容器中,该容器最多容纳 6 个面包。
- 当面包容器满的时候,生产者不能再生产面包,而应该等待消费者消费面包;当消费者把容器中的面包消费完后,消费者不能再消费面包,而应该等待生产者生产面包。
- 面包总量为30。
角色:
生产者(Producer)、消费者(Consumer)、面包(Bread)、面包容器(BreadCcontainer);
思路:
创建两个线程,一个生产者线程,一个消费者线程。
生产者循环不停的生产面包,把面包放入面包容器中;消费者循环不停的消费面包,从面包容器中取出面包。
当容器中的面包数量为 6 时,调用生产者的wait() 方法,生产者进入等待队列,同时唤醒消费者开始消费面包;当面包容器中的面包数量为零时,调用消费者的wait() 方法,消费者进入等待队列,同时唤醒生产者开始生产面包。
代码:
生产者(Producer):
package basis.StuThread.StuNotify;
public class Producer implements Runnable {
private BreadContainer container;
public Producer(BreadContainer container) {
this.container = container;
}
@Override
public void run() {
for(int i=0;i<30;i++){
container.input(new Bread(i, Thread.currentThread().getName()));
}
}
}
消费者(Consumer):
package basis.StuThread.StuNotify;
public class Consumer implements Runnable {
private BreadContainer container;
public Consumer(BreadContainer container) {
this.container = container;
}
@Override
public void run() {
for(int i=0;i<30;i++){
container.output();
}
}
}
面包(Bread):
package basis.StuThread.StuNotify;
public class Bread {
private int id;
private String producerName;
public Bread(int id, String producerName) {
this.id = id;
this.producerName = producerName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProducerName() {
return producerName;
}
public void setProducerName(String producerName) {
this.producerName = producerName;
}
@Override
public String toString() {
return "Bread{" +
"id=" + id +
", producerName='" + producerName + '\'' +
'}';
}
}
面包容器(BreadCcontainer):
package basis.StuThread.StuNotify;
public class BreadContainer {
private Bread[] breads=new Bread[6];
private int index=-1;
//存入面包
public synchronized void input(Bread b){
while(index>=5){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index++;
breads[index]=b;
System.out.println(Thread.currentThread().getName()+"生成了"+b.getId());
this.notifyAll();
}
//取出面包
public synchronized void output(){
while(index<0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Bread b=breads[index];
breads[index]=null;
System.out.println(Thread.currentThread().getName()+"消费了:"+b.getId());
index--;
this.notifyAll();
}
}
测试类:
package basis.StuThread.StuNotify;
public class Test {
public static void main(String[] args) {
BreadContainer container=new BreadContainer();
Producer producer=new Producer(container);
Consumer consumer=new Consumer(container);
new Thread(producer, "生产者张三").start();;
new Thread(consumer, "消费者李四").start();;
}
}
结果(部分):
生产者张三生成了0
消费者李四消费了:0
生产者张三生成了1
消费者李四消费了:1
生产者张三生成了2
消费者李四消费了:2
生产者张三生成了3
生产者张三生成了4
生产者张三生成了5
生产者张三生成了6
生产者张三生成了7
生产者张三生成了8
消费者李四消费了:8
消费者李四消费了:7
消费者李四消费了:6
消费者李四消费了:5
消费者李四消费了:4
消费者李四消费了:3
二、使用ReentrantLock 实现线程通信
(1)Condition
ReentrantLock 通过Condition 接口实现线程通信。
Condition 接口将 Object 监视器方法(wait、 notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synichronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
(2)接口声明
package java.util.concurrent.locks;
public interface Condition {}
(3)常用方法
方法 | 描述 |
void await() | 使当前线程在接收到信号或被中断之前,一直处于等待状态。 |
boolean await(long time,TimeUnit unit) | 使当前线程在接收到信号、被中断或达到指定等待时间之前,一直处于等待状态。 |
long awaitNanos(long nanoTimeout) | 使当前线程在接收到信号、被中断或达到指定等待时间之前,一直处于等待状态。 |
void awaitUninterruptibly | 使当前线程在接收到信号之前,一直处于等待状态。 |
boolean awaitUnit(Data deadline) | 使当前线程在接收到信号、被中断或达到指定最后期限之前,一直处于等待状态。 |
void signal() | 唤醒一个等待线程 |
void signalAll() | 唤醒所有等待线程 |
(4)使用ReentrantLock 类实现生产者消费者问题
生产者(Producer)、消费者(Consumer)、面包(Bread)、以及测试类的代码与上述完全一样。
面包容器代码修改如下:
package basis.StuThread.StuReentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BreadContainer {
private Bread[] breads=new Bread[6];
private int index=-1;
//创建锁
private Lock lock=new ReentrantLock();
//创建两个Condition 条件队列
//生产者队列
Condition pro_condtion=lock.newCondition();
//消费者队列
Condition con_condtion=lock.newCondition();
//存入面包
public void input(Bread b){
lock.lock();
try {
while(index>=5){
try {
pro_condtion.await();//进入存的队列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index++;
breads[index]=b;
System.out.println(Thread.currentThread().getName()+"生成了"+b.getId());
con_condtion.signal();//唤醒消费者队列中的线程
} finally {
lock.unlock();
}
}
//取出面包
public void output(){
lock.lock();
try {
while(index<0){
try {
con_condtion.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Bread b=breads[index];
breads[index]=null;
System.out.println(Thread.currentThread().getName()+"消费了:"+b.getId());
index--;
pro_condtion.signal();//唤醒生产者队列中的线程
} finally {
lock.unlock();
}
}
}
这样我们就使用ReentrantLock 类实现了生产者消费者问题。其中
//生产者队列
Condition pro_condtion=lock.newCondition();
//消费者队列
Condition con_condtion=lock.newCondition();
ReentrantLock 中的 newCondition() 方法用于获取 Condition 的实现类对象,即一个等待队列。
(5)ReentrantLock 类分析
ReentrantLock 内部维护了一个 Sync 类型的锁对象。
private final Sync sync;
Sync 是 ReentrantLock类中的一个内部抽象类,继承了 AbstractQueuedSynchronizer 类。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
}
在锁框架中,AbstractQueuedSynchronizer抽象类可以毫不夸张的说,占据着核心地位,它提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。
ReentrantLock 类中有两种锁(静态内部类),一种是公平锁,一种是非公平锁(默认),两种锁类都继承了Sync类。
公平锁:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
非公平锁:
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}