• 21、多线程(线程与进程\线程的实现方式\线程的声明周期\线程同步与死锁\生产者消费者问题)


    一、线程基础内容

    1、程序、进程与线程

    1.1、程序:Program,是一个指令的集合

    1.2、进程:Process,(正在执行中的程序)是一个静态的概念

    • 进程是程序的一次静态态执行过程, 占用特定的地址空间(资源).
    • 每个进程都是独立的,由3部分组成cpu,data,code(执行逻辑)
    • 缺点:内存的浪费,cpu的负担

    1.3、线程:是进程中一个“单一的连续控制流程” (a singlesThread,equential flow of control)/执行路径

    • 线程又被称为轻量级进程(lightweight process)。
    • Threads run at the same time, independently of one another
    • 一个进程可拥有多个并行的(concurrent)线程
    • 一个进程中的线程共享相同的内存单元/内存地址空间--》可以访问相同的变量和对象,而且它们从同一堆中分配对象--》通信、数据交换、同步操作
    • 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信
    • 机制,这就使得通信更简便而且信息传递的速度也更快。
    一个进程中至少有一个线程

    • Java虚拟机启动的时候会有一个进程java.exe,该进程中
               至少有一个线程,在负责java程序的执行。而且这个线程
               运行的代码存在亍main方法中,该线程称之为主线程。
               一个进程中的线程共享代码和数据空间
    •  线程结束,进程未必结束,但进程结束,线程一定结束
    • 进程中包含线程,线程是进程的一部分

    2、线程的创建和启动

    • 在Java中负责线程的这个功能的是Java.lang.Thread 这个类
    • 可以通过创建 Thread 的实例来创建新的线程。
    • 每个线程都是通过某个特定Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。
    • 通过调用Thead类的start()方法来启动一个线程。
    创建线程的方式一》继承Thread类 

     一个多线程的程序

    public class ThreadDemo01 extends Thread {
    
        @Override
        public void run() {
    //        super.run();
            for (int i=0; i<10; i++){
                System.out.println(Thread.currentThread().getName()+"--------------"+i);
            }
        }
    
        public static void main(String[] args) {
            ThreadDemo01 threadDemo01 = new ThreadDemo01();
            threadDemo01.start();
            for (int i=0; i<5; i++){
                System.out.println(Thread.currentThread().getName()+"==========="+i);
            }
    
        }
    }

    打印结果为:

    /*
    main===========0
    Thread-0--------------0
    main===========1
    Thread-0--------------1
    main===========2
    Thread-0--------------2
    main===========3
    Thread-0--------------3
    main===========4
    Thread-0--------------4
    Thread-0--------------5
    Thread-0--------------6
    Thread-0--------------7
    Thread-0--------------8
    Thread-0--------------9
    */
    /**
     * 实现多线程的时候:
     *      1、需要继承Thread类
     *      2、必须要重写run方法,指的是核心执行的逻辑
     *      3、线程在启动的时候,不要直接调用run方法,而是要通过start()来进行调用
     *      4、每次运行相同的代码,出来的结果可能不一样,原因在于多线程谁先抢占资源无法进行人为控制
    */

    另一种方式, 用的更多

    创建线程的方式二-->实现Runnable接口
    public class RunnableDemo implements Runnable {
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"--------------"+i);
            }
        }
    
        public static void main(String[] args) {
            RunnableDemo runnableDemo = new RunnableDemo();
            Thread thread = new Thread(runnableDemo);
            thread.start();
            for(int i =0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"==========="+i);
            }
        }
    }

    打印结果为:

    /*
    Thread-0--------------0
    main===========0
    Thread-0--------------1
    main===========1
    Thread-0--------------2
    main===========2
    Thread-0--------------3
    Thread-0--------------4
    Thread-0--------------5
    Thread-0--------------6
    Thread-0--------------7
    Thread-0--------------8
    Thread-0--------------9
    main===========3
    main===========4
    
    Process finished with exit code 0
    
    */
    /**
     *  第二种实现方式:使用了代理设计模式
     *      1、实现Runnable接口
     *      2、重写run方法
     *      3、创建Thread对象,将刚刚创建好的runnable的子类实现作为thread的构造参数
     *      4、通过thread.start()进行启动
     */
    /** 
     *    推荐使用第二种方式,
     *      1、java是单继承,将继承关系留给最需要的类
     *      2、使用runnable接口之后不需要给共享变量添加static关键字,每次创建一个对象,作为共享对象即可
     */

    思考买票过程: 同一张票可能会被n多个人抢票

    版本1:Thread创建

    public class TicketThread extends Thread{
    
        private int ticket = 5; // 一共5张票
    
        @Override
        public void run() {
              System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
        }
    
        public static void main(String[] args) {
            TicketThread t1 = new TicketThread();
            TicketThread t2 = new TicketThread();
            TicketThread t3 = new TicketThread();
            TicketThread t4 = new TicketThread();
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    public class TicketThread extends Thread{
    
        private int ticket = 5; // 一共5张票
    
        @Override
        public void run() {
            for(int i = 0;i<100;i++){
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
                }
            }
        }
    
        public static void main(String[] args) {
            TicketThread t1 = new TicketThread();
            TicketThread t2 = new TicketThread();
            TicketThread t3 = new TicketThread();
            TicketThread t4 = new TicketThread();
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    运行结果为:

    /*
    Thread-0正在出售第5张票
    Thread-3正在出售第5张票
    Thread-1正在出售第5张票
    Thread-2正在出售第5张票
    Thread-1正在出售第4张票
    Thread-0正在出售第4张票
    Thread-3正在出售第4张票
    Thread-0正在出售第3张票
    Thread-1正在出售第3张票
    Thread-1正在出售第2张票
    Thread-1正在出售第1张票
    Thread-2正在出售第4张票
    Thread-0正在出售第2张票
    Thread-3正在出售第3张票
    Thread-3正在出售第2张票
    Thread-0正在出售第1张票
    Thread-2正在出售第3张票
    Thread-2正在出售第2张票
    Thread-2正在出售第1张票
    Thread-3正在出售第1张票
    
    Process finished with exit code 0
    
    */

    每个线程都是 5 4 3 2 1

    每个对象实例化时都拥有ticket 这样一个属性值, 意味着每个堆空间都有一个5

    开启线程后, 每个对象都是操作当前堆空间里的这个5, 所以每个线程都是5 4 3 2 1

     static, 静态变量归属于类 而不归属实例对象

    public class TicketThread extends Thread{
    
        private static int ticket = 5; // 一共5张票
    
        @Override
        public void run() {
            for(int i = 0;i<100;i++){
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
                }
            }
        }
    
        public static void main(String[] args) {
            TicketThread t1 = new TicketThread();
            TicketThread t2 = new TicketThread();
            TicketThread t3 = new TicketThread();
            TicketThread t4 = new TicketThread();
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    打印结果为:

     4 3 2 1 5, 顺序不对, 有一个数据同步问题

    版本2:使用runnable

    public class TicketRunnable implements Runnable {
    
        private int ticket = 5;
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
                }
            }
        }
    
        public static void main(String[] args) {
            TicketRunnable ticket = new TicketRunnable();
            Thread t1 = new Thread(ticket);
            Thread t2 = new Thread(ticket);
            Thread t3 = new Thread(ticket);
            Thread t4 = new Thread(ticket);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    打印结果为:

     这里就不用static了, 一个实例,5个线程都是用的这一个ticket

     
    在用线程同步实现之前先说下代理的设计模式
    定义一个接口
    public interface KindWomen {
    
        /*
        * 抛媚眼
        * */
        public void makeEyesWithMen();
    
        public void playWithMen();
    
    }

    王婆是代理人

    /*
    *
    * 代理人
    * */
    public class WangPo implements KindWomen {
    
        private KindWomen kindWomen;
    
        public WangPo(){
            this.kindWomen = new PanJinLian();
        }
    
        public WangPo(KindWomen kindWomen){
            this.kindWomen = kindWomen;
        }
    
        @Override
        public void makeEyesWithMen() {
            this.kindWomen.makeEyesWithMen();
        }
    
        @Override
        public void playWithMen() {
            this.kindWomen.playWithMen();
        }
    }

    潘金莲真正做事的人

    public class PanJinLian implements KindWomen{
        @Override
        public void makeEyesWithMen() {
            System.out.println("潘金莲在抛媚眼");
        }
    
        @Override
        public void playWithMen() {
            System.out.println("潘金莲。。。。。");
        }
    }

    贾氏也是真正做事的人

    public class JiaShi implements KindWomen{
        @Override
        public void makeEyesWithMen() {
            System.out.println("贾氏抛媚眼");
        }
    
        @Override
        public void playWithMen() {
            System.out.println("贾事。。。。。");
        }
    }

    西门庆是客户

    public class XiMenQing {
        public static void main(String[] args) {
    
    //        WangPo wangPo = new WangPo();
    //        wangPo.playWithMen();
    //        wangPo.makeEyesWithMen();
    
            JiaShi jiaShi = new JiaShi();
            WangPo wangPo = new WangPo(jiaShi);
            wangPo.makeEyesWithMen();
            wangPo.playWithMen();
    
        }
    }

    王婆是代理人, 真正做事的是贾氏

     3、线程状态

    ▪ 新生状态
    – 用new关键字建立一个线程后,该线程对象就处于新生状态。
    – 处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态。
    ▪ 就绪状态
    – 处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分
    配CPU。
    – 当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称为“CPU调
    度”。
    ▪ 运行状态
    – 在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任何而死亡。
    – 如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。
    ▪ 阻塞状态
    – 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让
    出CPU并暂时停止自己运行,进入阻塞状态。
    – 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等
    待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从
    原来停止的位置开始继续执行。
    ▪ 死亡状态
    – 死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个,一个是正常运行
    的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过stop方法来终止一个
    线程【不推荐使用】;三是线程抛出未捕获的异常。
     
    api方法:

     

    public class ThreadApiDemo  implements Runnable{
        public static void main(String[] args) {
            //获取当前线程对象
            Thread thread = Thread.currentThread();
            //获取当前线程的名称
            System.out.println(thread.getName());
            //获取线程的id
            System.out.println(thread.getId());
            //获取线程的优先级,在一般系统中范围是0-10的值,如果没有经过设置的话,就是默认值5,有些系统是0-100
            System.out.println(thread.getPriority());
            //设置线程池的优先级
            /*
            * 优先级越高一定越先执行吗?
            *       不一定,只是优先执行的概率比较大而已
            * */
            thread.setPriority(6);
            System.out.println(thread.getPriority());
    
            ThreadApiDemo threadApiDemo = new ThreadApiDemo();
            Thread t1 = new Thread(threadApiDemo);
            System.out.println(t1.isAlive()); // 判断是否存活
            t1.start();
            System.out.println(t1.isAlive());
            System.out.println(t1.getPriority());
    //        for(int i = 0;i<5;i++){
    //            System.out.println(Thread.currentThread().getName()+"-----"+i);
    //        }
            System.out.println(t1.isAlive());
        }
    
        @Override
        public void run() {
    //        for(int i = 0;i<5;i++){
    //            System.out.println(Thread.currentThread().getName()+"-----"+i);
    //        }
        }
    }

     join方法:

    public class MyRun implements Runnable {
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"------"+i);
            }
        }
    }
    public class JoinTest {
        public static void main(String[] args) {
            MyRun run = new MyRun();
            Thread thread = new Thread(run);
            thread.start();
    
            for(int i = 0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"-----------------"+i);
                if(i==3){
                    try {
                        thread.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    }
    /*
    main-----------------0
    main-----------------1
    main-----------------2
    main-----------------3
    Thread-0------0
    Thread-0------1
    Thread-0------2
    Thread-0------3
    Thread-0------4
    Thread-0------5
    Thread-0------6
    Thread-0------7
    Thread-0------8
    Thread-0------9
    main-----------------4
    
    Process finished with exit code 0
    */

    main i==3 后, 执行join(), 从运行态到阻塞态

    MyRun线程开始执行,执行完成后轮到 main继续执行

     sleep()方法

    public class SleepTest {
        public static void main(String[] args) {
            MyRun run = new MyRun();
            Thread thread = new Thread(run);
            thread.start();
    
            for(int i = 0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"=============="+i);
                if(i==2){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    /*
    main==============0
    main==============1
    main==============2
    Thread-0------0
    Thread-0------1
    Thread-0------2
    Thread-0------3
    Thread-0------4
    Thread-0------5
    Thread-0------6
    Thread-0------7
    Thread-0------8
    Thread-0------9
    main==============3
    main==============4
    
    Process finished with exit code 0
    */

    main 到2 时 , 1sThread都执行完了,继续执行main 3 4

    yield()方法:

    public class YieldTest {
        public static void main(String[] args) {
            MyRun run = new MyRun();
            Thread thread = new Thread(run);
            thread.start();
    
            for(int i = 0;i<5;i++){
                if(i==2){
                    Thread.yield();
    //                thread.stop();
                    System.out.println(Thread.currentThread().getName()+"=============="+i+"礼让一次");
                }else{
                    System.out.println(Thread.currentThread().getName()+"=============="+i);
    
                }
            }
        }
    }

    打印结果为:

    /*
    main==============0
    Thread-0------0
    main==============1
    Thread-0------1
    Thread-0------2
    Thread-0------3
    Thread-0------4
    Thread-0------5
    main==============2礼让一次
    main==============3
    Thread-0------6
    main==============4
    Thread-0------7
    Thread-0------8
    Thread-0------9
    
    Process finished with exit code 0
    */

    礼让一次后,又出现了main 3 又去竞争资源并且抢占到了资源

    stop()方法: 过时了,不推荐使用

    public class YieldTest {
        public static void main(String[] args) {
            MyRun run = new MyRun();
            Thread thread = new Thread(run);
            thread.start();
    
            for(int i = 0;i<5;i++){
                if(i==2){
    //                Thread.yield();
                    thread.stop();
                    System.out.println(Thread.currentThread().getName()+"=============="+i+"礼让一次");
                }else{
                    System.out.println(Thread.currentThread().getName()+"=============="+i);
    
                }
            }
        }
    }

    打印结果为:

    /*
    main==============0
    main==============1
    Thread-0------0
    Thread-0------1main==============2礼让一次
    main==============3
    main==============4
    
    Process finished with exit code 0
    */

    2 以后强制结束了

    父类Object 有几个非常重要的方法

    notify() 唤醒

    wait() 等待

    线程会有些误解, 因为先说的Object类, 前面说notify和wait会在多线程中说

    所有的类都有等待和唤醒的方法,也就是说时对象有等待和唤醒的方法,而不是说线程有等待和唤醒方法

    在多线程的时候,可以实现唤醒和等待的过程,但是唤醒和等待操作的对应不是thread类, 而是我们设置的共享对象或者共享变量

    public class Test1 implements Runnable{
    
    
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"====="+i);
                try {
                    Thread.sleep(1001);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            Test1 test1 = new Test1();
            Thread thread = new Thread(test1);
            thread.start();
            for(int i=10;i>0;i--){
                System.out.println(Thread.currentThread().getName()+"----"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    打印结果为:

    /*
    main----10
    Thread-0=====0
    main----9
    Thread-0=====1
    main----8
    Thread-0=====2
    main----7
    Thread-0=====3
    main----6
    Thread-0=====4
    main----5
    Thread-0=====5
    main----4
    Thread-0=====6
    main----3
    Thread-0=====7
    main----2
    Thread-0=====8
    main----1
    Thread-0=====9
    
    Process finished with exit code 0
    */

    二、线程同步

    /*
     *      多线程并发访问的时候回出现数据安全问题:
     *          解决方式:
     *              1、同步代码块
     *                  synchronized(共享资源、共享对象,需要是object的子类){具体执行的代码块}
     *              2、同步方法
     *                  将核心的代码逻辑定义成一个方法,使用synchronized关键字进行修饰,此时不需要指定共享对象
     *
     */
    */
    ▪ 同步的前提:
    ▪ (1)必须有两个或两个以上的线程
    ▪ (2)必须是多个线程使用同一资源
    ▪ (3)必须保证同步中只能有一个线程在运行
    多线程同时访问同一份资源, 这样才会出现线程安全问题
    加synchronized相当于加了一把锁, 只能有一个线程对它访问
     

    买票问题

    1、同步代码块

    public class TicketRunnable2 implements Runnable {
    
        private int ticket = 5;
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (this){
                    if (ticket > 0) {
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            TicketRunnable2 ticket = new TicketRunnable2();
            Thread t1 = new Thread(ticket,"A");
            Thread t2 = new Thread(ticket,"B");
            Thread t3 = new Thread(ticket,"C");
            Thread t4 = new Thread(ticket,"D");
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

     打印结果为:

    /*
    D正在出售第5张票
    C正在出售第4张票
    A正在出售第3张票
    B正在出售第2张票
    A正在出售第1张票
    
    Process finished with exit code 0
    */

    2、同步方法

    public class TicketRunnable3 implements Runnable {
    
        private int ticket = 5;
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.sale();
            }
        }
    
        /*
         * 使用同步方法解决多线程数据安全的问题
         * */
        public synchronized void sale() {
    
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
            }
        }
    
        public static void main(String[] args) {
            TicketRunnable3 ticket = new TicketRunnable3();
            Thread t1 = new Thread(ticket, "A");
            Thread t2 = new Thread(ticket, "B");
            Thread t3 = new Thread(ticket, "C");
            Thread t4 = new Thread(ticket, "D");
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    打印结果为:

    /*
    B正在出售第5张票
    C正在出售第4张票
    D正在出售第3张票
    A正在出售第2张票
    D正在出售第1张票
    
    Process finished with exit code 0
    */
    ▪ 同步监视器
      – synchronized(obj){}中的obj称为同步监视器
      – 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
      – 同步方法中无需指定同步监视器,因为同步方法的监视器是this,也就是该对象本身
    ▪ 同步监视器的执行过程
      – 第一个线程访问,锁定同步监视器,执行其中代码
      – 第二个线程访问,发现同步监视器被锁定,无法访问
      – 第一个线程访问完毕,解锁同步监视器
      – 第二个线程访问,发现同步监视器未锁,锁定并访问

    三、死锁

    ▪ 同步可以保证资源共享操作的正确性,但是过多同步也会产生死锁
    ▪ 死锁一般情况下表示互相等待,是程序运行时出现的一种问题

    四、线程的生产者与消费者

    ▪ 生产者不断生产,消费者不断取走生产者生产的产品 

    ▪ 生产者生产产品放到一个区域中,之后消费者从此区域里取出产品,区域值的是共享资源
    /*
     *
     * 生产产品,将产品放置到共享空间中
     *
     * */
    public class Producer implements Runnable {
    
    }
    /*
    *
    * 从共享空间中取走产品
    * */
    public class Consumer implements Runnable {
    
    }
    public class Goods {
    
        private String brand; // 品牌
        private String name; // 名称
    
        public String getBrand() {
            return brand;
        }
    
        public void setBrand(String brand) {
            this.brand = brand;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

    1、版本一

    保证生产者先生产产品

    public class Producer implements Runnable {
    
        private Goods goods;
    
        public Producer(Goods goods) {
            this.goods = goods;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if (i % 2 == 0) {
                    goods.setBrand("娃哈哈");
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    goods.setName("矿泉水");
                } else {
                    goods.setBrand("旺仔");
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    goods.setName("小馒头");
    
                }
                System.out.println("生产者生产了" + this.goods.getBrand() + "--" + this.goods.getName());
            }
        }
    }
    /*
    *
    * 从共享空间中取走产品
    * */
    public class Consumer implements Runnable {
    
        private Goods goods;
    
        public Consumer(Goods goods) {
            this.goods = goods;
        }
    
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者取走了"+this.goods.getBrand()+"----"+this.goods.getName());
            }
        }
    }
    /*
    * 多线程访问的时候出现了数据安全的问题
    *   1、生产者没有生产商品,消费者就可以获取
    *   2、商品的品牌和名称对应不上
    *
    * */
    public class Test {
        public static void main(String[] args) {
    
            Goods goods = new Goods();
    
            Producer producer = new Producer(goods);
            Consumer consumer = new Consumer(goods);
    
            Thread t1 = new Thread(producer);
            Thread t2 = new Thread(consumer);
            t1.start();
            t2.start();
    
    
        }
    }

    打印结果为:

     

     2、版本二

    同步方法解决, 不需要指定共享对象

    public class Goods {
    
        private String brand;
        private String name;
    
        public String getBrand() {
            return brand;
        }
    
        public void setBrand(String brand) {
            this.brand = brand;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        //消费者获取商品
        public synchronized void get(){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());
        }
        //生产者生产商品
        public synchronized void set(String brand,String name){
           this.setBrand(brand);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setName(name);
            System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName());
        }
    }
    /*
     *
     * 生产产品,将产房放置到共享空间中
     *
     * */
    public class Producer implements Runnable {
    
        private Goods goods;
    
        public Producer(Goods goods) {
            this.goods = goods;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if (i % 2 == 0) {
                    goods.set("娃哈哈","矿泉水");
                } else {
                    goods.set("旺仔","小馒头");
                }
            }
        }
    }
    /*
    *
    * 从共享空间中取走产品
    * */
    public class Consumer implements Runnable {
    
        private Goods goods;
    
        public Consumer(Goods goods) {
            this.goods = goods;
        }
    
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                goods.get();
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
    
            Goods goods = new Goods();
    
            Producer producer = new Producer(goods);
            Consumer consumer = new Consumer(goods);
    
            Thread t1 = new Thread(producer);
            Thread t2 = new Thread(consumer);
            t1.start();
            t2.start();
        }
    }

    打印结果为:

     还是存在问题,下面都是消费者,但是品牌和名称已经对应起来了

    刚刚生产过程不是一个原子操作,把生产过程已经放到了set了

     这里是一个原子操作了,加了synchronized,这里是不会发生中断的,品牌和名称要么全部成功,要么全部失败

    所以品牌和名称会全部对应起来,已经解决了一个问题

    3、版本三

    我们要改变的只有goods方法

    先判断只有生产者先生产消费者才能消费

    public class Goods {
    
        private String brand;
        private String name;
        //默认是不存在商品的,如果值等于true的话,代表有商品
        private boolean flag = false;
    
        public String getBrand() {
            return brand;
        }
    
        public void setBrand(String brand) {
            this.brand = brand;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        //消费者获取商品
        public synchronized void get(){
            /*
            * 如果flag等于false的话,意味着生产者没有生产商品,此时消费者无法消费,需要让消费者线程进入到阻塞状态,等待生产者生产,当
            * 有商品之后,再开始消费
            * */
            if (!flag){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());
            flag = false;
            //唤醒生产者去进行生产
            notify();
        }
        //生产者生产商品
        public synchronized void set(String brand,String name){
            //当生产者抢占到cpu资源之后会判断当前对象是否有值,如果有的话,以为着消费者还没有消费,需要提醒消费者消费,同时
            //当前线程进入阻塞状态,等待消费者取走商品之后,再次生产,如果没有的话,不需要等待,不需要进入阻塞状态,直接生产即可
            if(flag){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           this.setBrand(brand);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setName(name);
            System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName());
            //如果代码执行到此处,意味着已经生产完成,需要将flag设置为true
            flag = true;
            //唤醒消费者去进行消费
            notify();
        }
    }

    打印结果为:

     4、版本4

    上面基本是完成了生产者消费者

    我们代码里就设置了一个Goods对象,对象只有 1 个

    在现实中,共享空间不可能只有 1 个, 肯定不合适

    要放一个集合在这

    集合该如何处理??? 

    集合判断一堆的等待一堆的唤醒,非常麻烦

    这样就有了JUC

    java util 下有一个concurrent

     这里面就帮我们解决并发问题

    队列的方式

    import java.util.concurrent.BlockingQueue;
    
    public class ProducerQueue implements Runnable {
    
        private BlockingQueue<Goods> blockingQueue;
    
        public ProducerQueue(BlockingQueue blockingQueue) {
            this.blockingQueue = blockingQueue;
        }
    
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                Goods goods = null;
                if(i%2==0){
                    goods = new Goods("娃哈哈","矿泉水");
                }else{
                    goods = new Goods("旺仔","小馒头");
                }
                System.out.println("生产者开始生产商品:"+goods.getBrand()+"--"+goods.getName());
                try {
                    blockingQueue.put(goods);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    import java.util.concurrent.BlockingQueue;
    
    public class ConsumerQueue implements Runnable {
    
        private BlockingQueue<Goods> blockingQueue;
    
        public ConsumerQueue(BlockingQueue blockingQueue) {
            this.blockingQueue = blockingQueue;
        }
    
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                try {
                    Goods goods = blockingQueue.take();
                    System.out.println("消费者消费的商品是:"+goods.getBrand()+"--"+goods.getName());
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class Goods {
    
        private String brand;
        private String name;
    
        public Goods(String brand, String name) {
            this.brand = brand;
            this.name = name;
        }
    
        public String getBrand() {
            return brand;
        }
    
        public void setBrand(String brand) {
            this.brand = brand;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    public class Test {
        public static void main(String[] args) {
            BlockingQueue<Goods> queue = new ArrayBlockingQueue<Goods>(5);
            ProducerQueue producerQueue = new ProducerQueue(queue);
            ConsumerQueue consumerQueue = new ConsumerQueue(queue);
            new Thread(producerQueue).start();
            new Thread(consumerQueue).start();
    
    
        }
    }

    打印结果为:

    高级GUC

    跳转路径

  • 相关阅读:
    哈希表(python)
    双端循环列表实现栈(python)
    链表实现队列(python)
    循环双端链表(python)
    单链表(python)
    LRU(最近最少使用)(python实现)
    Ajax在Django框架中简单应用2
    图书管理系统增删改查
    Jenkins接入LDAP
    安装python3.6
  • 原文地址:https://www.cnblogs.com/kongxiangqun/p/16164402.html
Copyright © 2020-2023  润新知