• 17.多线程


    多线程

    线程

    线程: 是进程中的单个顺序控制流程, 是一条执行路径

    • 单线程: 一个进程如果只有一条执行路径, 则称为单线程程序
    • 多线程: 一个进程如果有多条执行路径, 则称为多线程程序

    多线程的实现

    方式一: 继承Thread

    重写MyThread类中的run方法

    MyThread

    package thread;
    
    public class MyThread extends Thread {
        @Override
        public void run() {
            for(int i=0; i<100; i++){
                System.out.println(i);
            }
        }
    }
    
    

    MyThreadDemo

    package thread;
    
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            // my1.run();
            // my2.run();
            // run方法同步执行
    
            // void start(): 导致此线程开始执行; Java虚拟地调用此线程的run方法
            my1.start();
            my2.start();
        }
    }
    
    

    注意:

    为什么重写run方法

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

    run()方法和start()方法的区别

    • run(): 封装线程执行的代码, 直接调用, 相当于普通方法的调用
    • start(): 启动线程, 有JVM调用此线程的run()方法

    设置和获取线程名称

    设置和获取线程名字的两个方法:

    • void setName(String name): 将此线程的名称更改为等于参数name

    • String getName(): 返回此线程的名称

    • 通过构造方法可以设置线程名称

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

    • public static Thread currentThread(): 返回当前正在执行线程对象的引用

    MyThread

    package thread.getThread;
    
    public class MyThread extends Thread {
        public MyThread() {
        }
    
        public MyThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            for(int i=0; i<100; i++){
                System.out.println(getName() + ":" + i);
            }
        }
    }
    
    /*
    源码分析
    private String name;
    
    public Thread() {
        this(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    public Thread(String name) {
        this(null, null, name, 0);
    }
    
    public Thread(ThreadGroup group, Runnable target, String name,
                      long stackSize) {
        this(group, target, name, stackSize, null, true);
    }
    
    private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals){
       this.name = name;
    }
    
    private static int threadInitNumber; // 0,1,2
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
    
    public final String getName() {
        return name;
    }
    
     */
    
    

    MyThreadDemo

    package thread.getThread;
    
    public class MyThreadDemo {
        public static void main(String[] args) {
            /*
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            // void setName(String name): 设置线程名称
            my1.setName("高铁");
            my2.setName("飞机");
    
             */
    
            MyThread my1 = new MyThread("高铁");  // 带参构造方法需要在自己的类中实现带参构造方法
            MyThread my2 = new MyThread("飞机");
    
            // void start(): 导致此线程开始执行; Java虚拟地调用此线程的run方法
            //my1.start();
            //my2.start();
    
            // static Thread.currentThread(): 返回当前正在执行的对象的引用
            System.out.println(Thread.currentThread().getName());  // main
        }
    }
    
    

    线程调度

    线程有两中调度模型

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

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

    多线程程序的执行是有随机性的,谁抢到CPU的使用权是不一样的

    Thread类中设置和获取线程优先级的方法

    • public final int getPriority(): 返回此线程的优先级
    • public final void setPriority(int newPriority): 更改此线程的优先级

    注意:

    • 线程默认优先级为5, 线程优先级范围为: 1-10
    • 线程优先级仅仅表示获取CPU时间片的几率高

    ThreadPriority

    package thread.getPriority;
    
    public class ThreadPriority extends Thread {
        @Override
        public void run() {
            for(int i=0; i<100; i++) {
                System.out.println(getName() + "," + i);
            }
        }
    }
    
    

    ThreadPriorityDemo

    package thread.getPriority;
    
    public class ThreadPriorityDemo {
        public static void main(String[] args) {
            ThreadPriority tp1 = new ThreadPriority();
            ThreadPriority tp2 = new ThreadPriority();
            ThreadPriority tp3 = new ThreadPriority();
    
            tp1.setName("高铁");
            tp2.setName("飞机");
            tp3.setName("汽车");
            // 获取线程优先级
            System.out.println(tp1.getPriority());
            System.out.println(tp2.getPriority());
            System.out.println(tp3.getPriority());
            // 默认优先级5
    
            // 设置优先级
            // public final void setPriority(int newPriority): 更改线程优先级
            // tp1.setPriority(10000);  // IllegalArgumentException
            // tp1.setPriority(Thread.MAX_PRIORITY);  // 10
            // tp2.setPriority(Thread.MIN_PRIORITY);  // 1
            // tp3.setPriority(Thread.NORM_PRIORITY);  // 5
    
            // 设置正确的优先级
            tp1.setPriority(5);
            tp2.setPriority(10);
            tp3.setPriority(1);
    
            tp1.start();
            tp2.start();
            tp3.start();
        }
    }
    
    

    线程控制

    方法名 说明
    static void sleep(long millis) 使用当前正在执行的线程停留(暂停)指定的毫秒数
    void join() 等待这个线程死亡
    void setDeamon(boolean on) 将此线程标记为守护线程, 当运行的线程都是守护线程时,Java虚拟机将退出

    示例

    ThreadSleep

    package thread.threadSleep;
    
    public class ThreadSleep extends Thread {
        @Override
        public void run() {
            for(int i=0; i<100; i++) {
                System.out.println(getName() + "," + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    ThreadSleepDemo

    package thread.threadSleep;
    
    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();
        }
    }
    
    

    Threadjoin

    package thread.threadSleep;
    
    public class ThreadJoin extends Thread {
        @Override
        public void run() {
            for(int i=0; i<100; i++) {
                System.out.println(getName() + "," + i);
            }
        }
    }
    
    

    ThreadJoinDemo

    package thread.threadSleep;
    /*
        void join(): 等待这个线程死亡
     */
    
    public class ThreadJoinDemo {
        public static void main(String[] args) {
            ThreadJoin ts1 = new ThreadJoin();
            ThreadJoin ts2 = new ThreadJoin();
            ThreadJoin ts3 = new ThreadJoin();
    
            ts1.setName("康熙");
            ts2.setName("四阿哥");
            ts3.setName("八阿哥");
    
            ts1.start();
            try {
                ts1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ts2.start();
            ts3.start();
        }
    }
    
    

    ThreadDaemon

    package thread.threadSleep;
    
    public class ThreadDaemon extends Thread {
        @Override
        public void run() {
            for(int i=0; i<100; i++) {
                System.out.println(getName() + "," + i);
            }
        }
    }
    
    

    ThreadDaemonDemo

    package thread.threadSleep;
    /*
        void setDaemon(boolean on): 将此线程标记为守护线程, 当运行的线程都是守护线程, Java虚拟机将退出
     */
    
    public class ThreadDaemonDemo {
        public static void main(String[] args) {
            ThreadDaemon td1 = new ThreadDaemon();
            ThreadDaemon td2 = new ThreadDaemon();
    
            td1.setName("关羽");
            td2.setName("张飞");
            // 设置主线程为刘备
            Thread.currentThread().setName("刘备");
    
            // 设置守护线程
            td1.setDaemon(true);
            td2.setDaemon(true);
    
            td1.start();
            td2.start();
            for(int i = 0; i<10; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    
    

    线程生命周期

    image-20201022143343281

    多线程实现二

    实现Runnable接口

    MyRunnable

    package thread.threadRealize;
    
    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i=0; i<100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    

    MyRunnableDemo

    package thread.threadRealize;
    
    public class MyRunnableDemo {
        public static void main(String[] args) {
            // 创建MyRunnable类的对象
            MyRunnable mr = new MyRunnable();
    
            // 创建Thread类的对象, 把MyRunnable对象作为构造方法的参数
            // Thread (Runnable target)
            // Thread t1 = new Thread(mr);
            // Thread t2 = new Thread(mr);
    
            // Thread (Runnable target, String name)
            Thread t1 = new Thread(mr, "高铁");
            Thread t2 = new Thread(mr, "飞机");
    
            t1.start();
            t2.start();
        }
    }
    
    

    相比于继承Thread类, 实现Runnable接口的好处

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

    线程同步

    卖票案例

    案例1

    SellTicket

    package thread.threadSync.sellTicket;
    
    public class SellTicket implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            // 1. 判断票数大于0, 告知哪个窗口卖出
            // 2. 卖出票后, 总票数减1
            // 3. 飘飘没有了, 也可能有人来问, 保证卖票动作一直执行
            while (true) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
    }
    
    

    SellTicketDemo

    package thread.threadSync.sellTicket;
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建SellTicket类
            SellTicket st = new SellTicket();
    
            // 创建三个Thread类的对象, 把SellTicket对象作为构造方法的参数, 并给出对应的窗口名称
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    案例进阶思考

    SellTicket

    package thread.threadSync.sellTicket2;
    
    public class SellTicket implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            // 相同的票出现了多次
            while (true) {
                /*
                // tickets = 100时
                if (tickets > 0) {
                    // sleep方法模拟出票时间
                    try {
                        Thread.sleep(100);
                        // t1休息100ms
                        // t2抢到cpu执行权, t2开始执行, 到这里 t2休息100ms
                        // t3抢到cpu执行权, t3开始执行, 到这里 t3休息100ms
                    } 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--;
    
                 */
    
                // 当tickets = 1时
                if (tickets > 0) {
                    // sleep方法模拟出票时间
                    try {
                        Thread.sleep(100);
                        // t1休息100ms
                        // t2抢到cpu执行权, t2开始执行, 到这里 t2休息100ms
                        // t3抢到cpu执行权, t3开始执行, 到这里 t3休息100ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 假设按照顺序醒来
                    // t1抢到CPU执行权限, 在控制态输出, 窗口1正在出售第1张票
                    // t1继续拥有cpu执行权限, 就会执行tickets--;操作, tickets=0
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    // t2抢到CPU的执行权, 在控制台输出: 窗口2正在出售第1张票
                    // t2继续拥有cpu执行权限, 就会执行tickets--;操作, tickets=-1
                    // t3抢到CPU的执行权, 在控制台输出: 窗口3正在出售第-1张票
                    // t3继续拥有cpu执行权限, 就会执行tickets--;操作, tickets=-2
                    tickets--;
                }
            }
        }
    }
    
    

    SellTicketDemo

    package thread.threadSync.sellTicket2;
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建SellTicket类
            SellTicket st = new SellTicket();
    
            // 创建三个Thread类的对象, 把SellTicket对象作为构造方法的参数, 并给出对应的窗口名称
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    卖票案例线程数据安全问题

    出现问题的原因

    • 是否是多线程
    • 是否有共享数据
    • 是否有多条语句操作共享数据

    如何解决安全问题

    • 基本思想: 破坏安全问题的环境

    实现方案:

    • 给多条语句操作共享数据的代码锁起来

    同步代码块

    锁定多条语句操作共享数据, 可以使用同步代码块实现

    • 格式
    synchronized(任意对象) {
    	多条语句操作共享数据的代码
    }
    
    • synchronize(任意对象): 就相当于给代码加锁, 任意对象就可以看成是一把锁

    SellTicket

    package thread.threadSync.sellTicketLock;
    
    public class SellTicket implements Runnable {
        private int tickets = 100;
        private Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                synchronized (obj) {
                    if (tickets > 0) {
                        // sleep方法模拟出票时间
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                }
            }
        }
    }
    
    

    SellTicketDemo

    package thread.threadSync.sellTicketLock;
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建SellTicket类
            SellTicket st = new SellTicket();
    
            // 创建三个Thread类的对象, 把SellTicket对象作为构造方法的参数, 并给出对应的窗口名称
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    同步的好处和弊端

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

    同步方法

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

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

    同步方法的锁对象是: this

    同步静态方法: 将synchronized关键字加到静态方法上

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

    同步静态方法的锁对象是: 类名.class

    SellTicket

    package thread.threadSync.sellTicketLock2;
    
    public class SellTicket implements Runnable {
        // private int tickets = 100;
        static int tickets = 100;
        private Object obj = new Object();
        private int x = 0;
        @Override
        public void run() {
            while (true) {
                if (x%2==0){
                    // synchronized (obj) {
                    // synchronized (this) {
                    synchronized (SellTicket.class) {
                        if (tickets > 0) {
                            // sleep方法模拟出票时间
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                            tickets--;
                        }
                    }
                } else {
    //                synchronized (obj) {
    //                    if (tickets > 0) {
    //                        // sleep方法模拟出票时间
    //                        try {
    //                            Thread.sleep(100);
    //                        } catch (InterruptedException e) {
    //                            e.printStackTrace();
    //                        }
    //                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
    //                        tickets--;
    //                    }
    //                }
                    sellTicket();
                }
                x++;
            }
        }
    
        /*
        private void sellTicket() {
            synchronized (obj) {
                if (tickets > 0) {
                    // sleep方法模拟出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
    
         */
        /*
        // 同步方法的同步锁 this
        private synchronized void sellTicket() {
            if (tickets > 0) {
                // sleep方法模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    
         */
    
        // 静态方法的同步锁 this
        private static synchronized void sellTicket() {
            if (tickets > 0) {
                // sleep方法模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
    
    

    SellTicketDemo

    package thread.threadSync.sellTicketLock2;
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建SellTicket类
            SellTicket st = new SellTicket();
    
            // 创建三个Thread类的对象, 把SellTicket对象作为构造方法的参数, 并给出对应的窗口名称
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    线程安全的类

    StringBuffer

    • 线程安全, 可变字符序列
    • JDK5后被StringBuilder替代. 通常使用StringBuilder, 支持所有相同操作, 而且更快, 不执行同步

    Vector

    • 实现了List接口, 与新的集合实现不同, Vector被同步. 如果不需要实现线程安全, 建议使用ArrayList代替Vector

    Hashtable

    • 该类实现了一个哈希表, 将键映射到值
    • 实现了Map接口, 与新的集合实现不同, Hashtable被同步. 如果不需要使用线程安全, 建议使用HashMap代替

    Demo

    package thread.threadSync.threadSafeClass;
    
    import java.util.*;
    
    public class ThreadSafeClass {
        public static void main(String[] args) {
            StringBuffer sb = new StringBuffer();
            StringBuilder sb2 = new StringBuilder();
    
            Vector<String> v = new Vector<String>();
            ArrayList<String> arrayList = new ArrayList<String>();
    
            Hashtable<String, String> ht = new Hashtable<String, String>();
            HashMap<String, String> hm = new HashMap<String, String>();
    
            // 多线程中StringBuilder, ArrayList, HashMap会有线程安全问题
            // static <T> list<T> synchronizedList(List<T> list) 返回由指定列表支持的同步(线程安全)列表
            List<String> list = Collections.synchronizedList(new ArrayList<String>());
        }
    }
    
    

    Lock锁

    JDK5后提供了新的锁对象Lock, 更直接清晰的表达如何加锁和释放锁

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

    Lock中提供了锁和释放锁的方法

    • void lock(): 加锁
    • void unlock(): 释放锁

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

    ReentrantLock的构造方法

    • ReentrantLock(): 创建一个ReentrantLock的实例

    SellTicket

    package thread.lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SellTicket implements Runnable {
        // private int tickets = 100;
        static int tickets = 100;
        private Lock lock = new ReentrantLock();
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock();
                    if (tickets > 0) {
                        // sleep方法模拟出票时间
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    

    SellTicketDemo

    package thread.lock;
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            // 创建SellTicket类
            SellTicket st = new SellTicket();
    
            // 创建三个Thread类的对象, 把SellTicket对象作为构造方法的参数, 并给出对应的窗口名称
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            // 启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    生产者消费者

    生产着消费者模式

    生产和消费解耦合, 生产者不在依赖消费者, 从提高程序运行效率

    • 生产者: 数据的产生方
    • 消费者: 数据的消费者

    为了实现解耦, 通常会采用共享的数据区域, 就想一个仓库

    • 生产者生产数据后, 将数据放在共享区域, 不需要关心消费者
    • 消费者只需要从共享区域获取数据,无需关系生产者的行为

    java提供了几个方法实现等待和唤醒, 在Object类中

    方法名 说明
    void wait() 导致当前线程等待, 知道另一个线程调用改对象的notify()方法和notifyAll()方法
    void notify() 唤醒正在等待对象检视器的单个线程
    void notifyAll() 唤醒正在等待对象检视器的所有线程

    生产者消费者案例

    生产者消费者案例中的类

    • 奶箱(Box):
    • 生产者类(Producer): 实现Runnable接口, 重写run()方法
    • 消费者类(Customer): 实现Runnable接口, 重写run()方法
    • 测试类(BoxDemo): 里面有main方法,

    Box

    package thread.producerCustomer;
    
    public class Box {
        private int milks;
        // 定义一个成员变量表示奶箱装填
        private boolean state = false;
    
        // 提供存储和消费牛奶操作
        public synchronized void put(int milks) {
            // 如果有牛奶, 等待消费
            if(state) {
                try {
                    wait();
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            this.milks = milks;
            System.out.println("送奶工将第" + this.milks + "瓶奶放入奶箱");
             // 生产完毕后, 修改奶箱状态
            state = true;
            // 唤醒其他等待的线程
            notifyAll();
        }
    
        public synchronized void get() {
            // 如果没有牛奶, 等待生产
            if (!state) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("用户消费" + this.milks + "瓶奶");
    
            // 消费完毕后, 修改奶箱状态
            state = false;
            // 唤醒其他等待的线程
            notifyAll();
        }
    }
    
    

    Producer

    package thread.producerCustomer;
    
    public class Producer implements Runnable {
        private Box b;
    
        public Producer() {
        }
        public Producer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            for(int i=1; i<5; i++) {
                b.put(i);
            }
        }
    }
    
    

    Customer

    package thread.producerCustomer;
    
    public class Customer implements Runnable {
        private Box b;
    
        public Customer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            while (true) {
                b.get();
            }
        }
    }
    

    Demo

    package thread.producerCustomer;
    
    public class BoxDemo {
        public static void main(String[] args) {
            // 创建奶箱对象
            Box b = new Box();
    
            // 创建生产者
            Producer p = new Producer(b);
            // 创建消费者
            Customer c = new Customer(b);
    
            // 创建两个线程, 分别把生产者和消费者对象作为构造方法参数传递
            Thread t1 = new Thread(p);
            Thread t2 = new Thread(c);
    
            t1.start();
            t2.start();
    
        }
    }
    
  • 相关阅读:
    Csharp: ASP.NET Core 3.1 Razor Pages Query and Pagination
    人脸识别示例
    C# 调用WCF服务的两种方法
    linux磁盘空间满处理情况
    linux上PGI编译器安装
    jeecgboot中自定义sql分页实现
    顺畅访问github的一种新思路和方案
    eduYouke在线教育点播系统
    基于SpringBoot的在线教育系统【源码开源】【建议收藏】
    解决ThinkPHP6 控制器不存在:app\controller\Index
  • 原文地址:https://www.cnblogs.com/ryxiong-blog/p/13890454.html
Copyright © 2020-2023  润新知