有生产者和消费者模型引出线程synchronized 、wait、notify、notifyAll、sleep和volatile

普通线程(extends Thread)的几个重点

  • synchronized () { }
  • sysynchronized
  • wait()
  • notify()
  • notifyAll()
  • sleep()
  • volatile

接下来将从生产者和消费者模型中进行学习


首先,明白什么是生产者和消费者模型,简单介绍一则实例,通过实例的要求明白(请原谅我自己给自己编的题目太Low):

  1. 【生产者】负责生产商品,商品不断的存入仓库
  2. 【消费者】负责消费商品,商品不断被取出并消费
  3. 【仓库】仓库最大容量为5
  4. 【要求1】生产者和消费者是并行的
  5. 【要求2】只要库存不为零,消费者就可以进行消费
  6. 【要求3】只要库存未满,生产者就可以生产
  7. 【要求4】一旦消费者消费时,发现库存为0,立马进行报告生产者生产

(自己编的题目,原谅一下可能不是很好理解)

然后,分析题目进行编码:

根据题目显然,我们需要仓库类,生产者类,消费者类,生产者线程类,消费者线程类,测试类

仓库类:
根据需求显然仓库就是一个队列容器,而且是先进先出的特性,使用ArrayDeque简直好到不能再好

import java.util.ArrayDeque;
public class Repertory {
    //公共商品队列容器
    volatile public static ArrayDeque<String> goods = new  ArrayDeque<String>();
}

生产者类:

public class Producer {
    public void produce(String goodsName){
        System.out.println("生产者生产了"+goodsName);
        //像仓库中添加生产的商品
        Repertory.goods.add(goodsName);
        //打印仓库的库存信息,展示现在未被消费的商品
        System.out.println("\t----"+Repertory.goods);
    }
}

消费者类:

public class Consumer {
    public void consume(){
        //判断队列容器的首元素是否为null
        if(Repertory.goods.getFirst() != null){
            //poll()操作,队列首元素出队,进行消费
            System.out.println("消费者消费了:"+Repertory.goods.poll());
        }       
    }
}

生产者线程类:

public class ProducerThread extends Thread{
    private Producer producer;
    public ProducerThread(Producer producer) {
        this.producer = producer;
    }
    @Override
    public void run() {
        try {
            int i = 0;
            while (true) {
                synchronized("lock"){
                    //开始生产
                    this.producer.produce("商品"+i++);
                    //生产周期为1秒
                    Thread.sleep(1000);
                    //如果库存容量达到最大容量5,执行wait(),立马释放锁,消费者拿到锁,进行消费
                    if(Repertory.goods.size()==5){
                        "lock".wait();
                    }
                }
            }
        } catch (Exception e) {
            // TODO: handle exception
        }   
    }
}

消费者线程类:

public class ConsumerThread extends Thread{
    private Consumer consumer;
    public ConsumerThread(Consumer consumer) {
        this.consumer = consumer;
    }
    @Override
    public void run() {
        try {
            while(true){
                synchronized ("lock") {
                    //如果库存容量大于0,则立马进行消费
                    if(Repertory.goods.size()>0){
                        this.consumer.consume();
                        //消费周期为0.5秒
                        Thread.sleep(500);
                    }else {
                        //如果库存容量小于等于0,则立马进行唤醒生产者线程,释放锁,生产线程拿到锁,进行生产
                        System.out.println("没有商品呢,还不补货...");
                        "lock".notify();
                        Thread.sleep(1000);
                    }
                }   
            }
        } catch (Exception e) {
            // TODO: handle exception
        }   
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Producer producer = new Producer();
        new ProducerThread(producer).start();
        Consumer consumer = new Consumer();
        new ConsumerThread(consumer).start();
    }
}

测试结果:

生产者生产了商品0
    ----[商品0]
生产者生产了商品1
    ----[商品0, 商品1]
生产者生产了商品2
    ----[商品0, 商品1, 商品2]
生产者生产了商品3
    ----[商品0, 商品1, 商品2, 商品3]
生产者生产了商品4
    ----[商品0, 商品1, 商品2, 商品3, 商品4]
消费者消费了:商品0
消费者消费了:商品1
消费者消费了:商品2
消费者消费了:商品3
消费者消费了:商品4
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品5
    ----[商品5]
生产者生产了商品6
    ----[商品5, 商品6]
消费者消费了:商品5
生产者生产了商品7
    ----[商品6, 商品7]
生产者生产了商品8
    ----[商品6, 商品7, 商品8]
生产者生产了商品9
    ----[商品6, 商品7, 商品8, 商品9]
生产者生产了商品10
    ----[商品6, 商品7, 商品8, 商品9, 商品10]
消费者消费了:商品6
消费者消费了:商品7
消费者消费了:商品8
消费者消费了:商品9
消费者消费了:商品10
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品11
    ----[商品11]
消费者消费了:商品11
没有商品呢,还不补货...
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品12
    ----[商品12]
消费者消费了:商品12
没有商品呢,还不补货...
没有商品呢,还不补货...
生产者生产了商品13
    ----[商品13]
消费者消费了:商品13
生产者生产了商品14
    ----[商品14]
消费者消费了:商品14
生产者生产了商品15
    ----[商品15]
消费者消费了:商品15
生产者生产了商品16
    ----[商品16]
生产者生产了商品17
    ----[商品16, 商品17]
消费者消费了:商品16

程序一定要仔细分析,重要和不重要的地方都写了注释,应该还是很好理解的,一定要理解完了,然后我们开启新的解释。


线程的概念

线程是一个程序内部的顺序控制流,每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数 器(PC),线程切换的开销小。多线程就是在同一应用程序中有多个顺序流同时执行。

线程的生命周期

线程的生命周期:一个线程从它创建到启动,然后运行,直到最后执行完的整个过程。
新建状态:即创建一个新的线程对象,注意新创建的线程对象如果没有调用start()方法将永远得不到运行。
就绪状态:当新的线程对象调用start()方法时,就进入了就绪状态,进入就绪状态的线程不一定立即就开始运行。
运行状态:进入运行状态的线程,会由CPU处理其线程体中的代码。
阻塞状态:运行状态的线程有可能出现意外情况而中断运行,比如进行IO操作,内存的读写,等待键盘输入数据(注意不是出错,出错将提前终止线程)而进入阻塞状态。当阻塞条件解除后,线程会恢复运行。但其不是立即进入运行状态,而是进入就绪状态。
终止状态:当线程中run()方法语句执行完后进入终止状态。

这里写图片描述

synchronized () { }

在Java中,引入对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为”互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
用于方法声明中,标明整个方法为同步方法这时候他锁定的对象是this。当执行该方法语句时,当前线程有可能被中断,但对象还是被当前线程锁定着,其它线程可以进入运行状态,但不能访问这个对象。如果要用到该对象,其它线程只能等待。这样的方法也叫同步方法。

sysynchronized

用于修饰语句块,标明整个语句块为同步块。锁定的对象范围小,可以提高程序的运行效率,避免死锁。它还可以锁定不同的对象,不一定只锁定当前对象this。

wait()
注:wait会释放锁
当前线程必须拥有此对象的monitor(即锁),才能调用某个对象的wait()方法能让当前线程阻塞,(这种阻塞是通过提前释放synchronized锁,重新去请求锁导致的阻塞,这种请求必须有其他线程通过notify()或者notifyAll()唤醒重新竞争获得锁)

notify()
注:notify仅仅只是通知,不释放锁
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;

notifyAll()

notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁

sleep()

线程休眠:暂停执行当前运行中的线程,使之进入阻塞状态,待经过指定的”延迟时间’后再醒来并转入到就绪状态。

volatile

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。

volatile关键字保证了操作的可见性,但是volatile能保证对变量的操作是原子性吗?

答案是:volatile也无法保证对变量的任何操作都是原子性的。


关于volatile和sysynchronized,在后期学到并发编程中会学到,到时候必须要讲到三大要素:原子性、可见性和有序性,等学到了,再进行补上。

猜你喜欢

转载自blog.csdn.net/xiaheshun/article/details/79933195