Java-28-多线程

41 多线程

我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?

要解决上述问题,咱们得使用多进程或者多线程来解决.

41.1 并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。
    在这里插入图片描述

在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

41.2 线程与进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:

进程

在这里插入图片描述

线程

在这里插入图片描述

线程调度:

  • 分时调度

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

    • 设置线程的优先级

在这里插入图片描述

  • 抢占式调度详解

    大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

    实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
    其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

在这里插入图片描述

41.3 创建线程类

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

代码如下:

测试类:

package com.tipdm.Demo03;

/**
 * 创建多线程程序的第一种方式:创建Thread类的子类
 * java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
 *
 * 实现步骤:
 *      1. 创建一个Thread类的子类
 *      2. 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
 *      3. 创建Thread类的子类对象
 *      4. 调用Thread类中的方法start方法,开始新的线程,执行run方法
 *           void start()   使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
 *           结果是两个线程并发地运行;当前线程(main)和另一个线程(创建的新线程)。
 *           多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
 *      java程序属于抢占式调度,哪个优先级高优先给哪个线程优先执行;同一个优先级,随机选择一个执行。
 */
public class demo2 {
    public static void main(String[] args) {
        //3. 创建Thread类的子类对象
        MyThread myThread = new MyThread();
//        4. 调用Thread类中的方法start方法,开始新的线程,执行run方法
        myThread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main:" + i);
        }
    }
}

自定义线程类:

package com.tipdm.Demo03;

public class MyThread extends Thread{
    public MyThread() {
        super();
    }

    public MyThread(String name) {
        super(name);
    }

    //2. 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run:" + i);
        }
    }
}

41.4 线程名称

41.4.1 获取线程名称

获取线程的名称:

  1. 使用Thread类中的方法getName()

​ String getName() 返回该线程的名称。

  1. 可以先获利到当前正在执行的线程,使用线程中的方法getName()获取线程的名称

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

自定义线程类:

package com.tipdm.Demo01;

/**
 * 获取线程的名称:
 *      1. 使用Thread类中的方法getName()
 *          String getName()  返回该线程的名称。
 *      2. 可以先获利到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
 *          static Thread currentThread()  返回对当前正在执行的线程对象的引用
 */
// 定义一个Thread类的子类
public class MyThread extends Thread{
    // 重写run方法

    @Override
    public void run() {
//        // 获取线程名称
//        String name = getName();
//        System.out.println(name);

//        Thread t = Thread.currentThread();
//        System.out.println(t);
//
//        String name = t.getName();
//        System.out.println(name);

        System.out.println(Thread.currentThread().getName());
    }
}

主类:

package com.tipdm.Demo01;

/**
 * 线程的名称:
 *      主线程:main
 *      新线程:Thread-0, Thread-1, Thread-2
 */
public class demo1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        new MyThread().start();
        new MyThread().start();

        System.out.println(Thread.currentThread().getName());  // 主线程名称
    }
}

41.4.2 设置线程名称

设置线程名称:(了解)

  1. 使用Thread类中的方法setName(名字)

​ void setName(String name) 改变线程名称,使之与参数 name 相同。

  1. 创建一个带参数的构造方法,参数传递线程的名称;

​ 调用父类的带参构造方法,让线程名称传递给父类,让父类(Thread)给子线程起一个名字

​ Thread(String name) 分配新的 Thread 对象

自定义线程类:

package com.tipdm.Demo02;

import java.util.TreeMap;

/**
 * 设置线程名称:(了解)
 *      1. 使用Thread类中的方法setName(名字)
 *          void setName(String name) 改变线程名称,使之与参数 name 相同。
 *      2. 创建一个带参数的构造方法,参数传递线程的名称;
 *         调用父类的带参构造方法,让线程名称传递给父类,让父类(Thread)给子线程起一个名字
 *         Thread(String name) 分配新的 Thread 对象
 */
public class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

主类:

package com.tipdm.Demo02;


public class demo1 {
    public static void main(String[] args) {
        // 开启多线程
        MyThread mt = new MyThread();
        mt.setName("小强");
        mt.start();

        // 开启多线程
        new MyThread("旺财").start();
    }
}

41.5 线程等待

package com.tipdm.Demo03;

/**
 * public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
 *
 */
public class demo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 60; i++) {
            System.out.println(i);
            // 使用Thread类的sleep方法,让程序睡眠1s
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

41.6 创建多线程的第二种方式

创建多线程程序的第二种方式:实现Runnable接口

java.lang.Runnable

Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。

java.lang.Thread类的构造方法

Thread(Runnable target)     分配新的 Thread 对象。
Thread(Runnable target, String name)    分配新的 Thread 对象。

实现步骤:

  1. 创建一个Runnable接口的实现类

  2. 在实现类中重写Runnable接口的run方法,设置线程任务

  3. 创建一个Runnable接口的实现类对象

  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象

  5. 调用Thread类中的start方法,开启新的线程执行run方法

实现Runnable接口创建多线程程序的好处:

  1. 避免了单继承的局限性

​ 一个类只能继承一个类(一个人只能由一个亲爹),类继承了Thread类就不能继承其他的类

​ 实现了Runnable接口,还可以继承其他的类,实现其他的接口

  1. 增强了程序的扩展性,降低了程序的耦合性(解耦)

​ 实现了Runnable接口的方式,把设置线程任务和开启新线程任务进行了分离(解耦)

​ 实现类中,重写了run方法:用来设置线程任务

​ 创建Thread类对象,调用start方法:用来开启新线程。

自定义线程类1:

package com.tipdm.Demo04;
//1. 创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
    // 2. 在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

自定义线程类2:

package com.tipdm.Demo04;
//1. 创建一个Runnable接口的实现类
public class RunnableImpl2 implements Runnable{
    // 2. 在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("helloWorld!" + i);
        }
    }
}

主类:

package com.tipdm.Demo04;

public class demo1 {
    public static void main(String[] args) {
        // 3. 创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        // 4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
//        Thread t = new Thread(run);  // 打印线程名称
        Thread t2 = new Thread(new RunnableImpl2());  // 打印线程名称
        // 5. 调用Thread类中的start方法,开启新的线程执行run方法
        t2.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

41.7 匿名内部类方式实现线程的创建

匿名内部类方式实现线程的创建

  • 匿名:没有名字
  • 内部类:写在其他类内部的类

匿名内部类作用:简化代码

把子类继承父类,重写父类的方法,创建子类对象合一步完成。

把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成

匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

格式:

new 父类/接口(){
重写父类/接口中的方法
}
package com.tipdm.Demo05;

public class demo1 {
    public static void main(String[] args) {
        // 线程的父类是Thread
        // new MyThread().start();
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                }
            }
        }.start();

        // 线程的接口Runnable
        // Runnable r = new RunnableImpl();  // 多态
        Runnable r = new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                }
            }
        };
        new Thread(r).start();

        // 简化接口的方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                }
            }
        }).start();
    }
}

41.8 线程安全

以电影院卖票为例:

image-20211224152531175

线程安全导致的结果:可能会出现同一张电影票被卖给了不同的用户。

实现卖票案例

线程实现类:

package com.tipdm.Demo06;

/**
 * 实现卖票案例
 */
public class RunnableImpl implements Runnable{
    // 定义一个多线程共享的票源
    private int ticket = 100;

    // 设置线程任务:卖票
    @Override
    public void run() {
        while(true){
            // 先判断票是否存在
            if(ticket > 0){
                // 票存在,卖票  ticket--
                System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
                ticket--;
            }else{
                break;
            }
            // 提高线程安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

主类:

package com.tipdm.Demo06;

/**
 * 模拟卖票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 */
public class demo1Ticket {
    public static void main(String[] args) {
        // 创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        // 调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
//        Thread-2--->正在卖第4张票
//        Thread-1--->正在卖第4张票
//        Thread-0--->正在卖第4张票
//        出现线程安全问题,出现重复的票
    }
}

41.8.1 解决线程安全问题——使用同步代码块

卖票案例出现了线程安全问题

卖出了不存在的票和重复的票

解决线程安全问题的一种方案:使用同步代码块

格式:

synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

注意:

  1. 通过代码块中的锁对象,可以使用任意的对象

  2. 但是必须保证多个线程使用的锁对象是同一个

  3. 锁对象作用:

把同步代码块锁住,只让一个线程在同步代码块中执行。

线程实现类:

package com.tipdm.Demo07;

public class RunnableImpl implements Runnable{
    // 定义一个多线程共享的票源
    private int ticket = 100;

    // 创建一个锁对象
    Object obj = new Object();

    // 设置线程任务:卖票
    @Override
    public void run() {
        while(true){
        // 同步代码块
        synchronized (obj){
            // 先判断票是否存在
            if(ticket > 0){
                // 票存在,卖票  ticket--
                System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
                // 提高线程安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
            }else{
                break;
            }
        }
        }
    }
}

主类:

package com.tipdm.Demo07;

/**
 * 模拟卖票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 */
public class demo1Ticket {
    public static void main(String[] args) {
        // 创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        // 调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

41.8.2 解决线程安全问题——使用同步方法

解决线程安全问题的第二种方案:使用同步方法

使用步骤:

  1. 把访问了共享数据的代码抽取出来,放到一个方法种

  2. 在方法上添加synchronized修饰符

格式:定义方法的格式

修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

线程实现类:

package com.tipdm.Demo08;

public class RunnableImpl implements Runnable{
    // 定义一个多线程共享的票源
    private int ticket = 100;

    // 设置线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:" + this);
        while(ticket>0){
            payTicket();
        }
    }

    /**
     * 定义一个同步方法
     * 同步方法也会把方法内部的代码锁住
     * 只让一个线程执行
     * 同步方法的锁对象是谁?
     * 就算实现类对象 new RunnableImpl()
     * 也就是this
     */
    public /*synchronized*/ void payTicket(){
        synchronized (this){
            // 先判断票是否存在
            if(ticket > 0){
                // 票存在,卖票  ticket--
                System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
                // 提高线程安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
            }
        }
    }
}

主类:

package com.tipdm.Demo08;

/**
 * 模拟卖票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 */
public class demo1Ticket {
    public static void main(String[] args) {
        // 创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        System.out.println("run:" + run);
        // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        // 调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

41.8.3 解决线程安全问题——使用静态同步方法

解决线程安全问题的第二种方案:使用静态同步方法

使用步骤:

  1. 把访问了共享数据的代码抽取出来,放到一个方法种

  2. 在方法上添加synchronized修饰符

格式:定义方法的格式

修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

线程实现类:

package com.tipdm.Demo09;

public class RunnableImpl implements Runnable{
    // 定义一个多线程共享的票源
    private static int ticket = 100;

    // 设置线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:" + this);
        while(ticket>0){
            payTicketStatic();
        }
    }

    /**
     * 静态的同步方法
     * 锁对象是谁?
     * 不能是this
     * this是创建对象之后产生的,静态方法优先于对象
     * 静态方法的锁对象是本类的class属性  -->  class文件对象(反射)
     */
    public static /*synchronized*/ void payTicketStatic(){
        synchronized (RunnableImpl.class) {
            // 先判断票是否存在
            if (ticket > 0) {
                // 票存在,卖票  ticket--
                System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
                // 提高线程安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
            }
        }
    }
}

主类:

package com.tipdm.Demo09;

/**
 * 模拟卖票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 */
public class demo1Ticket {
    public static void main(String[] args) {
        // 创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        System.out.println("run:" + run);
        // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        // 调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

41.8.4 解决线程安全问题——使用Lock锁

解决线程安全问题的第三种方案:使用Lock锁

java.util.concurrent.locks.Lock 接口

Lock接口种的方法:

​ void Lock() 获取锁

​ void unlock() 释放锁

java.util.concurrent.locks.ReentrantLock implements Lock接口

使用步骤:

  1. 在成员位置创建一个ReentrantLock对象

  2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

线程实现类:

package com.tipdm.Demo10;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RunnableImpl implements Runnable{
    // 定义一个多线程共享的票源
    private int ticket = 100;

    // 1. 在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

//    // 设置线程任务:卖票
//    @Override
//    public void run() {
//        while(ticket > 0){
//            l.lock();
//            // 票存在,卖票  ticket--
//            System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
//            // 提高线程安全问题出现的概率,让程序睡眠
//            try {
//                Thread.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            ticket--;
//            l.unlock();
//        }
//    }


    // 设置线程任务:卖票
    @Override
    public void run() {
        while(ticket > 0){
            l.lock();
            // 提高线程安全问题出现的概率,让程序睡眠
            try {
                // 票存在,卖票  ticket--
                System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
                ticket--;
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                l.unlock();  // 无论程序是否异常都会把锁释放,提高程序效率。
            }
        }
    }
}

主类:

package com.tipdm.Demo10;

/**
 * 模拟卖票案例
 * 创建3个线程,同时开启,对共享的票进行出售
 */
public class demo1Ticket {
    public static void main(String[] args) {
        // 创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        // 调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

41.9 线程状态

当线程被创建并启动后,它既不是一启动就进入了执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六个线程状态。

image-20211224153510798

image-20211224153454954

41.9.1 Timed Waiting(计时等待)

Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢?

在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。

其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待),那么我们通过一个案例加深对该状态的一个理解。

try{
	Thread.sleep(5000);
}catch(Eception e){
	e.printStackTrace();
}

41.9.2 Blocked(锁阻塞)

Blocked 状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。

我们已经学完同步机制,那么这个状态是非常好理解的了。比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。

image-20211224153950525

41.9.3 Waiting(无限等待)

Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。

等待唤醒案例:线程之间的通信

创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无线等待)

创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子。

注意:

  • 顾客和老板线程必须使用同步代码包裹起来,保证等待和唤醒只能有一个在执行
  • 同步使用的锁对象必须保证唯一
  • 只有锁对象才能调用wait和notify方法

Object类中的方法

  • void wait() 在其他线程调用此对象的notify()方法或 notifyAll()方法前,导致当前线程等待。
  • void notify() 唤醒在此对象监视器上等待的单个线程。 会继续执行wait方法之后的代码
package com.tipdm.Demo11;

public class demo1 {
    public static void main(String[] args) {
        // 创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                while(true){
                    // 告知老板要的包子的种类和数量
                    System.out.println("告知老板要的包子的种类和数量");

                    // 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        // 进入等待状态,等老板做包子
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("包子真香!!!!!!!!!!");
                    System.out.println("------------------");
                }
            }
        }.start();

        // 创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                while(true){
                    // 花了5秒做包子
                    try {
                        Thread.sleep(5000);
                        System.out.println("包子做好了!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj){
                        // 包子做好了唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

进入到TimeWaiting(计时等待)有两种方式

  1. 使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态

  2. 使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

package com.tipdm.Demo11;

public class demo2 {
    public static void main(String[] args) {
        // 创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                while(true){
                    // 告知老板要的包子的种类和数量
                    System.out.println("告知老板要的包子的种类和数量");

                    // 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        // 进入等待状态,等老板做包子
                        try {
                            obj.wait(5000);  // 就算不被唤醒,也将在5s后自动醒来
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("包子真香!!!!!!!!!!");
                    System.out.println("------------------");
                }
            }
        }.start();
    }
}

void notifyAll() 唤醒在此对象监视器上等待的所有线程。 会继续执行wait方法之后的代码

package com.tipdm.Demo11;

/**
 * void notifyAll() 唤醒在此对象监视器上等待的所有线程。   会继续执行wait方法之后的代码
 */
public class demo3 {
    public static void main(String[] args) {
        // 创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                while(true){
                    // 告知老板要的包子的种类和数量
                    System.out.println("顾客1,告知老板要的包子的种类和数量");

                    // 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        // 进入等待状态,等老板做包子
                        try {
                            obj.wait(5000);  // 就算不被唤醒,也将在5s后自动醒来
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("顾客1,包子真香!!!!!!!!!!");
                }
            }
        }.start();

        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                while(true){
                    // 告知老板要的包子的种类和数量
                    System.out.println("顾客2,告知老板要的包子的种类和数量");

                    // 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        // 进入等待状态,等老板做包子
                        try {
                            obj.wait(5000);  // 就算不被唤醒,也将在5s后自动醒来
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("顾客2,包子真香!!!!!!!!!!");
                }
            }
        }.start();

        // 创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                while(true){
                    // 花了5秒做包子
                    try {
                        Thread.sleep(5000);
                        System.out.println("包子做好了!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj){
                        // 包子做好了唤醒顾客吃包子
                        obj.notifyAll();  // 唤醒所有等待的线程
                    }
                }
            }
        }.start();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值