zoukankan      html  css  js  c++  java
  • 进程的并发执行(入门)

    1、实现多线程

    1.1 进程

    进程:是正在运行的程序,是系统进行资源分配和调用的独立单位

    每一个进程都有它自己的内存空间和系统资源

    1.2 线程

    线程:是进程中的单个顺序控制流,是一条执行路径
    单线程:一个进程如果只有一条执行路径,则称为单线程程序
    多线程:一个进程如果有多条执行路径,则称为多线程程序

    1.3 多线程的实现方式

    方式1:继承Thread类

    1. 定义一个类MyThread继承Thread类
    2. 在MyThread类中重写run()方法
    3. 创建MyThread类的对象
    4. 启动线程
    // 定义一个类MyThread继承Thread类
    public class MyThread extends Thread {
        public MyThread() {
        }
    
        public MyThread(String name) {
            super(name);
        }
    
        // 在MyThread类中重写run()方法
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(this.getName()+":"+i);
            }
        }
    }
    
    public class MyThreadDemo {
        public static void main(String[] args) {
            // 创建MyThread类的对象
            MyThread thread1 = new MyThread();
            // run()并没有真正启动线程,run()是封装线程执行的代码,直接调用,相当于普通方法的调用
            // thread1.run();
    
            // 启动线程
            // void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
            thread1.start();
        }
    }
    

    两个小问题:

    1. 为什么要重写run()方法?

      ​ 因为run()是用来封装被线程执行的代码

    2. run()方法和start()方法的区别?

      ​ run():封装线程执行的代码,直接调用,相当于普通方法的调用

      ​ start():启动线程;然后由JVM调用此线程的run()方法

    方式2:实现Runnable接口

    1. 定义一个类MyRunnable实现Runnable接口
    2. 在MyRunnable类中重写run()方法
    3. 创建MyRunnable类的对象
    4. 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    5. 启动线程
    // 定义一个类MyRunnable实现Runnable接口
    public class MyRunnable implements Runnable {
        // 在MyRunnable类中重写run()方法
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    
    public class MyRunnableDemo {
        public static void main(String[] args) {
            MyRunnable my = new MyRunnable();
    
            // Thread(Runnable runnable):构造方法,传入一个实现了Runnable接口的参数
            Thread t1 = new Thread(my);
            Thread t2 = new Thread(my);
    
            // 启动线程
            t1.start();
            t2.start();
        }
    }
    

    实现Runnable接口的好处

    1. 避免了Java单继承的局限性
    2. 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想

    1.4 设置和获取线程名称

    Thread类中设置和获取线程名称的方法

    • void setName(String name):将此线程的名称更改为等于参数name
    • String getName():返回此线程的名称
    • 通过构造方法也可以设置线程名称
    public class MyThread extends Thread {
        public MyThread(String name){
            super(name);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println(getName()+":"+i);
            }
        }
    }
    
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread mt1 = new MyThread("刘备");
            MyThread mt2 = new MyThread();
            MyThread mt3 = new MyThread();
    
            // 这里调用的是Thread类中的setName方法
            mt2.setName("关羽");
            mt3.setName("张飞");
    
            mt2.start();
            mt3.start();
        }
    }
    

    如何获取main()方法所在的线程名称

    • static Thread currentThread():返回当前正在执行的线程对象的引用
    public class MyThreadDemo {
        public static void main(String[] args) {
            // static Thread currentThread():返回当前正在执行的线程对象的引用
            System.out.println(Thread.currentThread().getName());
        }
    }
    

    1.5 线程调度

    线程有两种调度模型

    1. 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
    2. 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个优先级高的线程获取的CPU时间片相对多一些

    Java使用的是抢占式调度模型

    ​ 假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性的,因为谁抢到CPU的使用权是不一定的。

    线程优先级

    1. Thread类中设置和获取线程优先级的方法
      • public final int getPriority():返回此线程的优先级
      • public final void setPriority(int new Priority):更改此线程的优先级
    2. 线程默认优先级是5;线程优先级的范围是1-10
    3. 线程优先级高仅仅表现线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
    public class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(this.getName()+":"+i);
            }
        }
    }
    
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread thread1 = new MyThread();
            MyThread thread2 = new MyThread();
            MyThread thread3 = new MyThread();
    
            // 线程默认的优先级为5
            System.out.println(thread1.getPriority()); // 5
            System.out.println(thread2.getPriority()); // 5
            System.out.println(thread3.getPriority()); // 5
    
            // public final void setPriority(int new Priority):更改此线程的优先级
            // IllegalArgumentException:抛出表示一种方法已经通过了非法或不正确的参数
            // thread1.setPriority(10000);
    
            // Thread中有三个静态常量分别表示最高优先级、最低优先级和默认优先级
            System.out.println(Thread.MAX_PRIORITY);// 10
            System.out.println(Thread.MIN_PRIORITY);// 1
            System.out.println(Thread.NORM_PRIORITY);// 5
            // 设置正确的线程优先级
            thread1.setPriority(10);
            thread2.setPriority(1);
            thread3.setPriority(3);
    
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    

    1.6 线程控制

    static void sleep(long millis):使当前正在执行的线程停留(暂停执行)指定的毫秒数

    public class ThreadSleep extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class ThreadSleepDemo {
        public static void main(String[] args) {
            ThreadSleep ts1 = new ThreadSleep();
            ThreadSleep ts2 = new ThreadSleep();
            ThreadSleep ts3 = new ThreadSleep();
            ts1.setName("曹操");
            ts2.setName("刘备");
            ts3.setName("孙权");
            ts1.start();
            ts2.start();
            ts3.start();
        }
    }
    

    void join():等待这个线程死亡

    public class ThreadJoin extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName()+":"+i);
            }
        }
    }
    public class ThreadJoinDemo {
        public static void main(String[] args) {
            ThreadJoin tj1 = new ThreadJoin();
            ThreadJoin tj2 = new ThreadJoin();
            ThreadJoin tj3 = new ThreadJoin();
    
            tj1.setName("康熙");
            tj2.setName("四阿哥");
            tj3.setName("八阿哥");
    
            tj1.start();
            try {
                tj1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tj2.start();
            tj3.start();
        }
    }
    

    void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,JVM将退出

    public class ThreadDaemon extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName()+":"+i);
            }
        }
    }
    public class ThreadDaemonDemo {
        public static void main(String[] args) {
            ThreadDaemon td1 = new ThreadDaemon();
            ThreadDaemon td2 = new ThreadDaemon();
    
            td1.setName("关羽");
            td2.setName("张飞");
    
            Thread thread = Thread.currentThread();
            thread.setName("刘备");
            td1.setDaemon(true);
            td2.setDaemon(true);
            td1.start();
            td2.start();
    
            for (int i = 0; i < 10; i++) {
                System.out.println(thread.getName()+":"+i);
            }
        }
    }
    

    1.7 线程的生命周期

    线程的生命周期

    2、线程同步

    2.1 买票

    需求:

    • 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口买票,请设计一个程序模拟该电影院买票

    思路:

    • 定义一个SellTicket实现Runnable接口,里面定义一个成员变量private int tickets = 100;

    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下

      • 判断票数大于0,就卖票,并告知是哪个窗口卖的
      • 卖了票之后,总票数要减一
      • 票没有了,也有可能有人来问,所以这里用死循环让卖票动作一直执行
    • 定义一个测试类SellTicketDemo,里面有main方法,代码执行如下

      • 创建SellTicket类的对象

      • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名

      • 启动线程
    public class SellTicket implements Runnable {
        private int tickets = 100;
        @Override
        public void run() {
            while (true) {
                if (tickets> 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第"
                                       +tickets+"张票");
                    tickets--;
                }
            }
        }
    }
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket s = new SellTicket();
            Thread t1 = new Thread(s,"一号窗口");
            Thread t2 = new Thread(s,"二号窗口");
            Thread t3 = new Thread(s,"三号窗口");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    买票出现了问题

    相同的票出现了多次

    @Override
    public void run() {
        // 出现了相同的票
        while (true) {
            // tickets = 100;
            // t1,t2,t3
            // 假设t1线程抢到CPU的执行权
            if (tickets> 0) {
                // 通过sleep()方法来模拟出票时间
                try {
                    Thread.sleep(100);
                    // t1线程休息100毫秒
                    // t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里时,t2线程休息100毫秒
                    // t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里时,t3线程休息100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 假设线程按照顺序醒过来
                // t1抢到CPU的执行权,在控制台输出:窗口1正在出售第100张票
                System.out.println(Thread.currentThread().getName() + "正在出售第"
                                   +tickets+"张票");
                // t2抢到CPU的执行权,在控制台输出:窗口2正在出售第100张票
                // t3抢到CPU的执行权,在控制台输出:窗口3正在出售第100张票
                tickets--;
                // 如果这三个线程还是按照顺序来,这里就执行了3次--操作,最终票变为97
            }
        }
    }
    

    出现了负数的票

    @Override
    public void run() {
        // 出现了负数的票
        while (true) {
            // tickets = 100;
            // t1,t2,t3
            // 假设t1线程抢到CPU的执行权
            if (tickets > 0) {
                // 通过sleep()方法来模拟出票时间
                try {
                    Thread.sleep(100);
                    // t1线程休息100毫秒
                    // t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里时,t2线程休息100毫秒
                    // t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里时,t3线程休息100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 假设线程按照顺序醒过来
                // t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
                // 假设t1继续拥有CPU的执行权,就会执行ticket--的操作,ticket= 0
                // t2抢到了CPU的执行权,在控制台输出:窗口2正在出售第0张票
                // 假设t2继续拥有CPU的执行权,就会执行ticket--的操作,ticket= -1
                // t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
                // 假设t3继续拥有CPU的执行权,就会执行ticket--的操作,ticket= -2
                System.out.println(Thread.currentThread().getName() + "正在出售第"
                                   +tickets+"张票");
                tickets--;
            }
        }
    }
    

    问题原因

    线程执行的随机性导致的

    2.2 买票案例数据安全问题

    • 为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)
      • 是否是多线程环境
      • 是否有数据共享
      • 是否有多条语句操作共享数据
    • 如何解决多线程的安全问题?
      • 基本思路:让程序没有安全问题的环境
    • 怎么实现呢?
      • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
      • Java提供了同步代码块的方式来解决

    2.3 同步代码块

    锁多条语句操作共享数据,可以使用同步代码块实现
    格式:
    ​ synchronized(任意对象){
    ​ 多条语句操作共享数据的代码块
    ​ }
    synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

    public class SellTicket implements Runnable {
        private int tickets = 100;
        // 这个是锁对象,可以是任意对象
        // 例如Student s = new Studnent();Integer i = new Integer()都是可以的
        // 只不过在synchronized()中要输入相同的对象,以代表被synchronized包裹的代码是被同一把锁锁住的
        Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                // 传入任意对象即可,也可以用匿名类的方法
                // synchronized(new Object()){
                synchronized (obj) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第"
                                           + tickets + "张票");
                        tickets--;
                    }
                }
            }
        }
    }
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket s = new SellTicket();
            Thread t1 = new Thread(s,"一号窗口");
            Thread t2 = new Thread(s,"二号窗口");
            Thread t3 = new Thread(s,"三号窗口");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    同步的好处和弊端

    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,很耗费资源,无形中会降低程序的运行效率

    2.4 同步方法

    同步方法:就是把synchronized关键字加到方法上

    • 格式:修饰符 synchronized 返回值类型 方法名(方法参数){...}
    public class SellTicket implements Runnable {
        private int tickets = 100;
        private int x = 0;
        @Override
        public void run() {
            while (true) {
                if(x%2 == 0){
                    // 这里使用this关键字获取当前调用方法的对象
                    // 如果继续使用object的话会因为用的不是同一把锁导致没有同步执行
                    synchronized (this) {
                        if (tickets > 0) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()
                                               + "正在出售第" + tickets + "张票");
                            tickets--;
                        }
                    }
                }else{
                    sellTicket();
                }
                x++;
            }
        }
        // 同步代码锁的对象是this
        public synchronized void sellTicket() {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                                   + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket s = new SellTicket();
            Thread t1 = new Thread(s,"一号窗口");
            Thread t2 = new Thread(s,"二号窗口");
            Thread t3 = new Thread(s,"三号窗口");    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    同步方法的锁对象是什么?

    • this

    同步静态方法:就是把synchronized关键字加到静态方法上

    • 格式:修饰符 static synchronized 返回值类型 方法名(方法参数){...}

        public class SellTicket implements Runnable {
            // 静态方法调用静态变量,所以讲tickets用static修饰符修饰
            private static int tickets = 100;
            private int x = 0;
            @Override
            public void run() {
                while (true) {
                    if(x%2 == 0){
                        // 静态方法的锁的对象是类名.class
                        synchronized (SellTicket.class) {
                            if (tickets > 0) {
                                try {
                                    Thread.sleep(100);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName()
                                                   + "正在出售第" + tickets + "张票");
                                tickets--;
                            }
                        }
                    }else{
                        sellTicket();
                    }
                    x++;
                }
            }
            // 静态方法的锁的对象是类名.class
            public static synchronized void sellTicket() {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                                       + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
        public class SellTicketDemo {
            public static void main(String[] args) {
                SellTicket s = new SellTicket();
                Thread t1 = new Thread(s,"一号窗口");
                Thread t2 = new Thread(s,"二号窗口");
                Thread t3 = new Thread(s,"三号窗口");    
                t1.start();
                t2.start();
                t3.start();
            }
        }
    

    同步静态方法的锁对象是什么

    • 类名.class

    2.5 线程安全的类

    StringBuffer

    • 线程安全,可变的字符序列。
    • 从版本JDK5开始,这个类已经被一个等同的类补充了,它被设计为使用一个线程,StringBuilder。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步。

    Vector

    • 从Java2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector

    Hashtable

    • 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键值或值。
    • 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,成为Java Collections Framework的成员。与新的集合实现不同,Hashtable被同步。如果不需线程安全的实现,建议用HashMap代替Hashtable。
    /*
        线程安全的类
            StringBuffer
            Vector
            Hashtable
     */
    public class ThreadDemo {
        public static void main(String[] args) {
            // 一般线程安全类中只用StringBuffer,因为ArrayList和HashMap可以通过Collections的指定方法返回一个同步列表
            StringBuffer sb = new StringBuffer();
    
    
            List<String> list = Collections.synchronizedList(new ArrayList<String>());
            Vector<String> v = new Vector<>();
    
            Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
            Hashtable<String, String> ht = new Hashtable<>();
        }
    }
    

    2.6 Lock锁

    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁

    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

    Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

    • Lock中提供了获得锁和释放锁的方法
      • void lock():获得锁
      • void unlock():释放锁

    Lock是接口,不能直接实例化,这里采用它的实现类ReentrantLock来实例化

    • Reentrantlock的构造方法
      • ReentrantLock():创建一个ReentrantLock的实例

    因为代码运行中可能会出现异常,导致不能释放锁,所以使用try{}finally{}语句块

    public class SellTicket implements Runnable {
        private static int tickets = 100;
        // ReentrantLock():创建一个ReentrantLock的实例
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    // void lock():获得锁
                    lock.lock();
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() 
                                           + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                } finally {
                    // void unlock():释放锁
                    lock.unlock();
                }
    
            }
        }
    }
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket s = new SellTicket();
            Thread t1 = new Thread(s,"一号窗口");
            Thread t2 = new Thread(s,"二号窗口");
            Thread t3 = new Thread(s,"三号窗口");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    3、生产者消费者

    3.1 生产者消费者模式概述

    生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻

    所谓生产者消费者问题,实际上主要是包含了两类线程:

    • 一类是生产者线程用于生产数据
    • 一类是消费者线程用于消费数据

    为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

    • 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
    • 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
      • 生产者----→共享数据区域←----消费者

    为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中

    Object类的等待和唤醒方法:

    • void wait() 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
    • void notify() 唤醒正在等待对象监视器的单个线程。
    • void notifyAll() 唤醒正在等待对象监视器的所有线程。

    3.2 生产着消费者案例

    生产者消费者案例中包含的类:

    1. 奶箱类(Box):定义一个成员变量,表示第X瓶奶,提供存储牛奶和获取牛奶的操作
    2. 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
    3. 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
    4. 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
      • 创建奶箱对象,这是共享数据区域
      • 创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
      • 创建两个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
      • 启动线程
    // 1.奶箱类(Box):定义一个成员变量,表示第X瓶奶,提供存储牛奶和获取牛奶的操作
    public class Box {
        private int milk = 0;
        private static final int MAX_NUM = 50;
        private static final int MIN_NUM = 0;
    
        // IllegalMonitorStateException不拥有指定的监视器,方法不用synchronized修饰时抛出
        // 需要添加同步关键字synchronized
        // 写了等待还要写唤醒
        public synchronized void getMilk() {
            if (milk<=MIN_NUM) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("消费者拿到第" + milk + "瓶奶");
            milk--;
            // 不写唤醒最多各自执行一次之后,两条进程全部进入等待状态
            notify();
        }
    
        public synchronized void setMilk() {
            if (milk>=MAX_NUM) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            milk++;
            System.out.println("生产者将第" + milk + "瓶奶放入奶箱");
            notify();
        }
    }
    
    // 2.生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
    public class Producer implements Runnable {
    
        Box box;
    
        public Producer() {
        }
    
        public Producer(Box box) {
            this.box = box;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                box.setMilk();
            }
        }
    }
    
    // 3.消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
    public class Customer implements Runnable {
    
        Box box;
    
        public Customer() {
        }
    
        public Customer(Box box) {
            this.box = box;
        }
    
        @Override
        public void run() {
            while (true) {
                box.getMilk();
            }
        }
    }
    
    public class BoxDemo {
        public static void main(String[] args) {
            Box box = new Box();
            Producer p = new Producer(box);
            Customer c = new Customer(box);
            Thread t1 = new Thread(p);
            Thread t2 = new Thread(c);
    
            t1.start();
            t2.start();
        }
    }
    

    小结:

    • 共享数据区域的数据要进行监控,否则消费者会无限制的从共享数据区域拿到数据致使数据变成负数
  • 相关阅读:
    Hadoop 学习笔记 (十) hadoop2.2.0 生产环境部署 HDFS HA Federation 含Yarn部署
    hadoop 2.x 安装包目录结构分析
    词聚类
    Hadoop 学习笔记 (十一) MapReduce 求平均成绩
    Hadoop 学习笔记 (十) MapReduce实现排序 全局变量
    Hadoop 学习笔记 (九) hadoop2.2.0 生产环境部署 HDFS HA部署方法
    Visual Studio Code 快捷键大全(Windows)
    Eclipse安装教程 ——史上最详细安装Java &Python教程说明
    jquery操作select(取值,设置选中)
    $.ajax 中的contentType
  • 原文地址:https://www.cnblogs.com/clevergirl/p/11390077.html
Copyright © 2011-2022 走看看