线程知识总结

多任务处理:

           什么是多任务呢?多任务就是几个任务同时运行。在我们上网的时候,即可以聊天,也可以听音乐,也可以浏览网页。这就是多任务。它有个特点:就是在一个任务还没有做完的的时候,又去做另外的任务。

 

              在程序里的多任务处理是怎么样的呢?它也是指在同一个时候执行多个任务,它不必等到某个任务执行完之后,再去做其他的任务。CPU在处理的时候,先是执行一个任务的一段代码,然后接着执行另一个任务的一段代码。交替执行直到任务结束。

 

              现实生活中这样的例子是很多的。如:有个柜台只有一个营业员。这时候来了二个顾客。那么这个营业员应该怎么样去应对呢?她问第一个顾客要买什么商品,然后把商品给第一个顾客看;在第一个顾客看的时候,又去问第二个顾客要买什么商品,然后把商品给第二个顾客看。在第二个顾客看时候,又去问第一个顾客对商品满不满意。如果顾客说再看看,那么营业员又去问第二个顾客满不满意。如果第二个顾客说满意,营业员就叫顾客付款,然后结束第二个顾客的营业任务,然后再去问第一个顾客满不满意。如果第一个顾客也满意付款。那么整个任务就结束了。

 

 

多任务处理的2种类型:

 

多任务处理有两种类型:
- 基于进程
- 基于线程

 

进程是指一种 自包容 的运行程序,有自己的地址空间 ;

 

当我们打开windows任务管理器的时候,就有一个进程的选项卡。在这个选项卡里,我们可以发现有很多当前计算机正在运行的应用程序。这些正在运行的每一个应用程序都叫一个进程。它们都会占用一定的内存,都有自己的地址空间。

 

基于进程的特点是允许计算机同时运行两个或更多的程序。

 

在JAVA中是可以启动一个进程的。就是说通过JAVA代码,我们可以打开另一个应用程序

 

 

代码示例:

 

//创建一个操作系统进程,用指定的的应用程序去打开指定的文件

       ProcessBuilder process=new ProcessBuilder(

              "C:/WINDOWS/NOTEPAD.EXE",//应用程序地址

              "c:/2.txt");//文件地址

 

       try {

           process.start();//启动进程

       } catch (IOException e) {

           e.printStackTrace();

       }

 

在上面的示例中,我们是开启了一个进程,用指定地址的应用程序(这里是记事本),去打开指定的文件(这里是C盘下的2.txt)

 

 

线程

              和多进程不同,什么是多线程呢?线程是进程内部单一的一个顺序控制流 。所以一个进程包含很多线程。 基于线程的多任务处理环境中,线程是最小的处理单位

 

              我们在使用word文档时,打开一个文件菜单,这就是开启了一线程,把一行字选中,加粗,这也是一个线程。线程的概念其实很简单,线程就是程序运行时的路径,它决定将要执行什么代码。所以当我们运行 main 方法时,其实就已经开启了一个线程了。

 

              基于线程的多任务处理的优点

 

基于线程所需的开销更少

在多任务中,各个进程需要分配它们自己独立的地址空间

多个线程可共享相同的地址空间并且共同分享同一个进程

           网站是个很典型的多任务处理的例子,当不同的用户同时去访问一个网站时,网站就需要进行多任务处理。以前使用CGI(通用网关接口技术),CGI是一个多进程的技术,当一个用户去访问网站时,是开启一个进程,这样的话,当有多个用户同时访问时,那么就得开启多个进程。每一个进程都有自己独立的地址空间,所以这样的网站内存很快就消耗掉了。不但可连接的用户少,而且很容易受到恶意攻击。在JAVA里,采用的servlet技术,是一个基于多线程的技术,共享同一个进程,共享相同的地址空间,这样的话,当有多个用户同时访问时,性能不会下降多少。

 

进程间调用涉及的开销比线程间通信多

线程间的切换成本比进程间切换成本低

 

 

 

              那么什么又是单线程,什么又是多线程呢?

 

    代码示例:

       public static void main(String[] args){

        a();

        b();

        c();

    }

 

在上面的例子里,大家可以看出,在main方法里执行时,必须要等到a()方法执行完了之后,才能执行b()方法。b()方法执行完以后才能执行C()方法。这样在等到一个方法执行完以后再执行另一个方法的模式叫单线程。我以前用到的程序大部分都是单线程的。

 

那么多线程是怎么一回事呢?

 

在上面的例子,可以看出,多线程是多个方法同时执行,在a()方法执行时,同时执行b()方法,换句话说,b()方法不必等到a()方法执行完以后再去执行。这就是多线程。就是说在一个程序中,同时有多个执行路径存在。

 

《西游记》里孙语空去打妖精,这时候他一个人去打三个妖精,它要把一个妖精打死之后,才能去打第二个妖精,第二个妖精打死之后,再打第三个妖精。这时候它是单线程的。一个打妖精很累,它就拔三根毫毛,变成三个小孙悟空,一个小孙悟空打一个妖精,就是说它不必等到第一个妖精打死之后再去打第二个妖精,而是同时去打。这就叫多线程。

 

 

Java 程序启动时,一个线程立刻运行,该线程通常称为程序的主线程。也就是 main 方法运行时就产生一个主线程。

主线程的特点:

最先开始

最后结束

由他产生其他子线程

通常它必须最后完成执行,因为它执行各种关闭动作。

 

 

这在上例中,孙悟空就好比是主线程,它变出的三个小猴子是子线程。小猴子是由孙悟空这个主线程产生的,小猴子打死妖精之后,由孙悟空再把小猴子回收成毫毛。

 

代码示例:

    public static void main(String[] args){

       //获得当前线程对象,即主线程

       Thread t= Thread.currentThread ();

        System.out .println("当前线程是: "+t);

        //输出结果:"当前线程是: Thread[main,5,main]"

        t.setName("主线程"); //设置线程名字     

        System.out .println("当前线程名是: "+t);

//    输出结果是:"当前线程名是: Thread[主线程,5,main]"

 

 

        try {

          for (int i=0;i<3;i++) {

            System.out .println(i);

 

 

            Thread.sleep (1500);

          }

        }

        catch (InterruptedException e) {

          System.out .println("主线程被中断"); }

 

    }

 

在这个例子中,是单线程的程序,而main 方法所在的线程就是主线程。那么如何去通过这个主线程去启动一个子线程呢?

 

通过以下两种方法创建 Thread 对象:

1 - 声明一个 Thread 类的子类,并 覆盖 run() 方法。

 

public class Threads {

    public static void main(String[] args) {

       One one=new One();//创建线程对象

       one.start();//启动线程

for (int i=65;i<75;i++){//打印字母

           System.out .println((char)i);

       }

    }

}

 

class One extends Thread{//继承Thread线程对象

    public void run(){//启动线程后自动调用子类重写父类的run()方法

       for (int i=0;i<10;i++){

           System.out .println(i);

       }

    }

}

 

 

在上面的例子中定义了一个线程,并且重写了父类的run()方法。然后在main方法这个主线程中启动这个线程,启动一个线程使用线程对象的start()方法。这时候就用自动的调用父类的run()方法,循环打出一串数字。在main主线程里也有一个循环,是打出字母。因为是两个线程同时候运行,这时候打印的结果是数字和字线交替打印。说明主线程不必等到子线程结束以后,就继续执行下面的语句。有些同学可能会说了,既然启动线程是调用run()方法。那么我直接调用run()方法不就行了,为什么要调用start()方法呢?上面例子中,main()方法改为

 

One one=new One();//创建线程对象

       one.run();//启动线程

for (int i=65;i<75;i++){//打印字母

           System.out .println((char)i);

       }

不也一样吗?

 

       这样的想法是错误的。因为启动线程是使用start()方法。这时候在主线程之外另外开启了一个线程,就是说主线程不必等到子线程结束以后再进行其他的动作。所以这时候字母和数字循环打印。而如果直接调用run()方法,其实只是普通方法的调用,并没有开启一个线程,所以是单线程的程序。上面例子中,就不会是数字和字母交替打印,而是先把数字打印完毕,再打印字母。

 

2 、声明一个实现 Runnable 接口的类,并实现 run() 方法。

public class Threads {

    public static void main(String[] args) {

       Thread two=new Thread(new Two());//创建线程对象,而其中的参数是实现了Runnable接口的类对象。

       two.start();//启动线程

    }

}

 

class Two implements Runnable{//实现Runnable接口,并实现这个接口定义的run()方法

 

    public void run() {

       System.out .println("线程启动");

    }

 

}

 

上面的例子是由一个类去实现Runnable接口,然后实现Runnable里定义的run()方法。然后再定义一个Thread线程类对象,将这个实现了Runnable接口的类对象作为参数传过去。这样的话,就是把线程对象和这个实现了Runnable接口的类对象作了一个关联,在启动线程的时候,就是去执行实现了Runnable接口的类对象里重写的run()方法。

 

 

上面两种方法都可以实现线程的启动,但通常采用第二种方法启动线程,原因是如果一个类继承了另一个类的时候,那么就不能再继承其他类了,但接口可以多实现,所以实现接口的方式更加灵活一些。

 

线程启动之后,会有不同的状态,它有什么样的状态呢?

 

 

 

从上面的图可以看出线程经常处于暂时停止执行的状态,那么什么时候线程暂时停止执行呢?

u线程:

-线程优先级比较低,因此它不能获得 CPU 时间。

-使用 sleep( ) 方法使线程睡眠。

-通过调用 wait( ) 方法,使线程等待。

-通过调用 yield( ) 方法,线程已显式出让 CPU 控制权。(挂起)

-线程由于等待一个文件 I/O 事件被阻塞。

 

线程优先级

 

线程是有优先级的,就是说当两个线程同时启动或同时去访问一个对象时,优先级高的线程会拥有更高的访问权。线程的优先级有1—10级,数字越大,优先级越高。

 

uJava 中的线程优先级是在 Thread 类中定义的常量

-NORM_PRIORITY : 值为 5

-MAX_PRIORITY : 值为 10

-MIN_PRIORITY : 值为 1

u缺省优先级为 NORM_PRIORITY  5

u有关优先级的方法有两个:

-final void setPriority(int newp) : 修改线程的当前优先级

-final int getPriority() : 返回线程的优先级

 

 

代码示例:

public class Threads {

    public static void main(String[] args) {

       One one=new One();

       Thread two=new Thread(new Two());

 

       one.setPriority(1);//设置第一个线程优先级为1级

       two.setPriority(10); //设置第二个线程优先级为10级

 

       one.start();//启动第一个线程

       two.start();//启动第二个线程

    }

 

}

 

class One extends Thread{

    public void run(){

       System.out .println("第一个线程");

    }

}

 

class Two implements Runnable{

 

    public void run() {

       System.out .println("第二个线程");

    }

 

}

 

上面例子运行的结果是“第二个线程   第一个线程”。虽然第一个线程比第二个线程先调用start()方法,但由于第一个线程的线程优先级比第二个线程低,所以优先启动第二个线程。

 

 

线程同步

       《西游记》里孙悟空拔出两根毫毛,变出两个小猴子,一个小猴子去把妖精往死里打,而另一个小猴子去问妖精话。这样的话就有问题了,如果第一个小猴子把妖精打死了,那么第二个猴子就没得问了。

 

       在网站访问的时候,如果两个用户同时去访问同一个数据库的同一张表,如果一个用户去查询,而另一个用户去修改。那么第一个用户访问到的数据是修改之前的数据,还是修改之后的数据呢?

 

       所以,为了解决上面的问题,线程采用了同步的概念。

       为了确保在任何时间点一个共享的资源只被一个线程使用,使用了 同步

 

就是说当两个线程同时访问同一个对象的时候,只允许一个线程访问。所以使用同步可以保证任何时候只有一个线程访问一个方法或对象

 

u线程同步使用同步关键字 synchronized 来进行标识

 

代码示例:

              需求:模拟银行取款的业务。有两个顾客去取钱的业务。

 

 

public class BankTest {

 

    public static void main(String[] args) {

       Bank bank=new Bank();//产生银行对象

       Man man1=new Man(bank,1500);//产生第一个顾客对象

       Man man2=new Man(bank,1300);//产生第二个顾客对象

 

       try {

           Thread.sleep (3000);//主线程休眠3000毫秒

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

       System.out .println("银行最后余额"+bank.getMoney());//获得当前银行帐户最后余额

    }

 

}

 

 

class Bank{

 

    private int money=2000;

 

 

    public int getMoney(){

       return money;

    }

 

 

    public void setMoney(int pressMoney){

 

       if (pressMoney<=money){//当取走的钱小于当前金额时候,允许操作

           try {

              Thread.sleep (1000);//线程休眠1000毫秒

           } catch (InterruptedException e) {

              // TODO 自动生成 catch 块

              e.printStackTrace();

           }

 

           money-=pressMoney;//存款减少

           System.out .println("取走"+pressMoney);

       }

       else {//当取的钱大于帐户余额,拒绝操作

           System.out .println("帐户已超支,操作失败");

       }

       System.out .println("最后"+money);//打印最后金额

    }

}

 

 

class Man extends Thread{

    Bank bank;//银行帐户

    int money;//顾客要取金额

    Man1(Bank bank,int money){

       this .bank=bank;

       this .money=money;

       this .start();

    }

 

    //调用帐户取钱方法

    public void putMoney(){

 

       bank.setMoney(money);

    }

 

 

    public void run(){

 

       putMoney();

    }

}

以上代码运行结果:

取走1300

取走1500

最后-800

最后-800

银行最后余额-800

 

上面例子中,对银行而言,是不允许帐户余额为负数的,所以在帐户类的setMoney()方法中,先判断取的钱是否大于帐户余额,如果大于帐户余额则操作失败。但这时候,有两个顾客(Man类对象)线程同时对同一个帐户进行操作。因为它们取的钱都小于帐户余额,这样的话,两个顾客都允许对帐户操作,这样帐户余额都会减少,因为两个顾客取钱的数目总和大于帐户余额,所以最后的结果帐户余额就是负数。

 

那么怎么防止这种情况呢?解决办法就是在setMoney()方法前面加上一个同步的关键字synchronized 使当前帐户类对象同步,这样在一线程在访问帐户时,另一个线程就不能再对帐户进行操作了。

上面代码改为:

 

 

    public synchronized void setMoney(int pressMoney){

       …………

    }

 

再运行时,结果就变成:

取走1500

最后500

帐户已超支,操作失败

最后500

银行最后余额500

 

 

上面的方法叫同步方法,使用同步方法的时候,锁住的是本身这个类对象,所以一旦这个类对象被一个线程使用,那个这个类对象就被锁住,那么其他的同步方法也不能被其他线程所访问了。

 

和同步方法对应的,还有同步块。就是在一个方法里不要求全部同步,而只是在调用对象时,对这个对象同步,(锁住的是这个对象)这时候就得用同步块。同步块写在方法里。

 

 

代码示例:

class One {

    void display(int num) {

       System.out .print("" + num);

       try {

           Thread.sleep (1000);

       } catch (InterruptedException e) {

           System.out .println("中断");

       }

       System.out .println(" 完成");

    }

}

 

class Two implements Runnable {

    int number;

 

    One one;

 

    Thread t;

 

    public Two(One one_num, int n) {

       one = one_num;

       number = n;

       t = new Thread(this );

       t.start();

    }

 

    public void run() {

       synchronized (one) {//同步块,对one这个对象同步,当前线程对one这个对象进行访问时,就将这个对象锁住。

           one.display(number);

       }

    }

}

 

死锁

 

u当两个线程循环依赖于一对同步对象时将发生死锁。例如:

          一个线程进入对象 ObjA 上的监视器,而另一个线程进入对象 ObjB 上的监视器。如果 ObjA 中的线程试图调用 ObjB 上的任何 synchronized 方法,就将发生死锁。

u死锁很少发生,但一旦发生就很难调试

线程之间的通信:

              在上面《西游记》里孙悟空拔出两根毫毛,变出两个小猴子,一个小猴子去把妖精往死里打,而另一个小猴子去问妖精话。这时候就是两个线程去访问同一个对象,如果妖精这个对象加了同步的话,那么当问话的小猴子访问的时候,那么打妖精的小猴子就只有暂时停止运行,进行等待。等到问话的小猴子问完了以后,就会通知打妖精的小猴子——我问完了,你可以把它打死了,这时候打妖精的小猴子就继续进行打妖精的任务。象这样两个线程访问同一个对象,当一个线程访问时,其他线程进行等待,当访问线程放弃当前对象使用权时,通知其他线程这样的机制就叫线程之间的通信。

 

u Java 提供了一个精心设计的线程间通信机制,使用 wait() notify() notifyAll() 方法

u这些方法是作为 Object 类中的 final 方法实现的。所以不能被子类重写

u这三个方法仅在 synchronized 方法中才能被调用。

 

 

wait、notify、notifyAll都是Object类定义的方法,不要理解为线程类定义的方法,因为所有的类都是直接或间接继承于Object类的,所以所有的类都会拥有这三个方法。

 

uwait() 方法告知被调用的线程退出监视器并进入等待状态,直到其他线程进入相同的监视器并调用 notify( ) 方法。

unotify( ) 方法通知同一对象上第一个调用 wait( ) 线程。

unotifyAll() 方法通知调用 wait() 的所有线程,具有最高优先级的线程将先运行。

 

 

线程间通信最典型的例子就是生产者、消费者和仓库的关系。

 

代码示例:

public class Creates {

    public static void main(String[] args) {

       仓库 depot=new 仓库();//创建仓库对象

 

 

       new 生产者 (depot).start();

        new 消费者 (depot).start();

    }

}

 

class 仓库{

    boolean isfull;//判断当前仓库是否为空

}

 

class 生产者 extends Thread{

    仓库 depot;

    生产者(仓库 depot){

       this .depot= depot;

    }

 

 

    private   void create(){

 

       synchronized (depot){//线程同步块,对仓库对象同步

 

       if (depot.isfull==false ){//当仓库isfull为假时,可以进行生产

           try {

              Thread.sleep (1000);//休眠1000毫秒

 

              System.out .println("生产完毕");

 

              depot.isfull=true ;//仓库状态设置为满

 

              //唤醒在仓库x对象上等待的下一个线程

              depot.notify();

 

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

       }

       else {

           try {

              //等待方法,当前线程放弃对depot对象的使用权

              depot.wait();

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

       }

       }

    }

 

 

    public void run(){

       for (int i=0;i<10;i++){

           create();

       }

 

    }

}

class 消费者 extends Thread{

    仓库 depot;

    消费者(仓库 depot){

       this . depot = depot;

    }

 

    private   void 消费(){

 

       synchronized (depot){//线程同步块,对仓库对象同步

       if (depot.isfull==true ){//当仓库isfull为真时,可以进行消费

           try {

              Thread.sleep (1000);//休眠1000毫秒

 

              System.out .println("消费完毕");

 

              depot.isfull=false ;//仓库状态设置为空

 

              //唤醒在仓库x对象上等待的下一个线程

              depot.notify();

 

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

 

       }

       else {

           try {

              //等待方法,当前线程放弃对depot对象的使用权

              depot.wait();

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

       }

       }

    }

 

    public void run(){

       for (int i=0;i<10;i++){

           消费();

       }

    }

}

 

 

代码分析:

 

生产者负责生产,生产的东西放入仓库。当仓库空的时候,进行生产;当仓库满的时候,进行等待。消费者负责消费,当仓库满的时候,进行消费,当仓库空的时候,进行等待。这时候,当仓库空的时候,生产者生产。生产完毕,仓库满了,就通知消费者,可以进行消费了,同时自己暂时停止当前线程的执行,进行等待,并放弃仓库这个资源的使用权。消费者接到这个通知后,就进行消费,当消费到仓库空的时候,就通知生产者可以进行生产了,同时自己暂时停止当前线程的执行,进行等待,并放弃仓库这个资源的使用权。这样两个线程互相唤醒,相互等待。

 

 

sleep和wait的区别

 

              sleep和wait都是使线程暂时停止执行的方法,但它们有很大的不同,sleep是线程类Thread 的方法,它是使当前线程暂时睡眠,可以放在任何位置。而wait是Object类的方法,它是使当前线程暂时放弃对象的使用权进行等待,必须放在同步方法或同步块里。Sleep使用的时候,线程并不会放弃对象的使用权,即不会释放对象锁,所以在同步方法或同步块中使用sleep,一个线程访问时,其他的线程也是无法访问的。而wait是会释放对象锁的,就是当前线程放弃对象的使用权,让其他的线程可以访问。线程执行wait方法时,需要另一个线程调用notify进行唤醒。而sleep只是暂时休眠一定时间,时间到了之后,自动恢复运行,不需另外的线程唤醒。

 

 

下面的例子是模拟厨师、营业员和仓库的关系。加深同学们对线程同步的理解。

 

 

    需求:货架上有三个面包,而营业员要卖出六个。厨师不断的生产,营业员不断销售。

 

 

public class 多线程练习 {

    public static void main(String[] args) {

       仓库 仓库1 = new 仓库();

 

 

       营业员 营业员1 = new 营业员(仓库1);

       厨师 厨师1 = new 厨师(仓库1);

       营业员1.start();//启动营业员线程

       厨师1.start();//启动厨师线程

 

    }

 

}

 

class 仓库 {

    Object 正在生产的面包=new Object();

 

    int 已完成面包 = 3;

    int 要生产面包 = 6;

    int 库存 = 3;

    int 已销售面包 = 0;

 

    boolean isSell = true ;//营业员是否能够销售

 

}

 

class 营业员 extends Thread {

    仓库 仓库1;

 

    营业员(仓库 仓库1) {

       this .仓库1 = 仓库1;

    }

 

    public void run() {

       while (仓库1.isSell) {//当isSell为真的时候,可以进行销售.

           销售();

       }

       System.out .println("营业员工作完成");

    }

 

    public void 销售() {

       System.out .println("现有库存"+仓库1.库存);

       if (仓库1.已销售面包 < 仓库1.要生产面包) {//未销售完

 

           if (仓库1.库存 != 0) {// 仓库里还有面包,可以销售

              仓库1.已销售面包++;

              System.out .println("销售第" + 仓库1.已销售面包 + "面包");

              try {

                  Thread.sleep (2000);//销售一个面包要2000毫秒

              } catch (InterruptedException e) {

                  e.printStackTrace();

              }

 

              仓库1.库存--;

              System.out .println("第" + 仓库1.已销售面包 + "面包销售完毕");

           } else {//当仓库里没有面包时,营业员线程等待

              try {

                  synchronized (仓库1.正在生产的面包) {//对正在生产的面包同步

                     System.out .println("营业员等待……");

                     仓库1.正在生产的面包.wait();//营业员放弃对正在生产面包的使用权

                  }

 

              } catch (InterruptedException e) {

                  e.printStackTrace();

              }

           }

       }

       else {//当卖出六个之后将仓库1.isSell设为假

           仓库1.isSell=false ;

       }

    }

 

}

 

class 厨师 extends Thread {

    仓库 仓库1;

 

    厨师(仓库 仓库1) {

       this .仓库1 = 仓库1;

    }

 

    public void run() {

       while (仓库1.已完成面包 < 仓库1.要生产面包) {//未达到要生产面包数量时,继续生产

           生产();

       }

       System.out .println("厨师工作完成");

    }

 

    public void 生产() {

 

       仓库1.已完成面包++;

       System.out .println("生产第" + 仓库1.已完成面包 + "面包");

       try {

           Thread.sleep (5000);//生产一个面包要5000毫秒

           System.out .println("第" + 仓库1.已完成面包 + "面包生产完毕");

           仓库1.库存++;

           synchronized (仓库1.正在生产的面包){//对正在生产的面包同步

              仓库1.正在生产的面包.notify();//唤醒对正生产的面包这个对象等待的线程,这里是唤醒营业员线程

           }

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

 

    }

}

 

运行结果:

生产第4面包

现有库存3

销售第1面包

第1面包销售完毕

现有库存2

销售第2面包

第2面包销售完毕

现有库存1

销售第3面包

第4面包生产完毕

生产第5面包

第3面包销售完毕

现有库存1

销售第4面包

第4面包销售完毕

现有库存0

营业员等待……

第5面包生产完毕

生产第6面包

现有库存1

销售第5面包

第5面包销售完毕

现有库存0

营业员等待……

第6面包生产完毕

厨师工作完成

现有库存1

销售第6面包

第6面包销售完毕

现有库存0

营业员工作完成

 

 

代码分析:

 

       上面例子中,营业员要销售六个面包,库存有三个。所以厨师要再生产三个出来。库存有的时候,营业员可以销售,但营业员销售速度比厨师生产速度要快,所以销售完第四个面包时,第五个面包还没有生产出来。这时候,营业员就要等待,直到厨师把第五个面包生产出来,唤醒他之后,再进行销售。当厨师生产完第六个之后,任务完成,而营业员销售完第六个之后,任务才完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值