15_多线程

文章目录

  • OS中的基本概念
    • 进程(process)与线程(thread)
    • 串行(serial)、并行(parallel)与并发(concurrency)
    • 同步(synchronization)与异步(asynchronization)
  • java程序运行原理
    • java命令+主类类名运行原理
  • 多线程的实现方式一:继承Thread类
    • 获取和设置线程的名称
  • 线程的调度方式
    • 调度方式的分类
    • java中采用哪种调度方式
  • 线程的优先级
  • 线程控制API
    • 线程休眠sleep
    • 线程合并join
    • 线程礼让yield
    • 守护线程setDaemon
    • 线程中断stop(已过时)
  • 线程的生命周期
    • 线程的几种状态
  • 多线程实现方式二:实现Runnable接口
  • 方式一 VS 方式二
  • 多线程数据安全问题
    • 出现的情况
    • 产生原因
  • 解决多线程数据安全问题
    • synchronized
      • synchronized的细节问题
  • Lock锁
    • ReentrantLock可重入锁
  • 死锁
  • 生产者消费者模型
  • 线程间通信
    • wait与notify机制
    • wait()
    • notify()
    • notifyAll()
    • 为什么wait,notify,notifyAll方法不定义在Thread类中?

Java的代码都是在某一条执行路径下,按照顺序依次执行的,一条路径就是一个线程。

15_多线程_第1张图片

OS中的基本概念

进程(process)与线程(thread)

进程

  • 进程是操作系统进行资源调度与分配的基本单位
  • 正在运行的程序或者软件

线程

  • 进程中有多个子任务,每个子任务就是一个线程
  • 从执行路径的角度看, 一条执行路径就是一个线程
  • 线程是CPU进行资源调度与分配的基本单位

进程与线程的关系

  • 线程依赖于进程而存在
  • 一个进程中可以有多个线程(最少1个)
  • 线程共享进程资源
  • 举例: 迅雷, Word拼写

串行(serial)、并行(parallel)与并发(concurrency)

串行

  • 一个任务接一个任务按顺序执行

并行

  • 同一个时间点(时刻)上, 多个任务同时运行

并发

  • 同一时间段内,多个任务同时运行

15_多线程_第2张图片

同步(synchronization)与异步(asynchronization)


单道批处理:内存中只能运行一个进程

多道批处理:内存中可以运行多个进程, "同时"发生 (进程的上下文切换)

  • 进程的上下文切换
      1. 保护现场
      1. 恢复现场

现代操作系统:引入了线程

java程序运行原理

java命令+主类类名运行原理

  • java命令会启动jvm进程, jvm进程会创建一个线程(main线程)
  • 执行main线程里面的main方法
  • jvm是多线程的。除了main线程外,还有其他线程,起码还有一个垃圾回收线程

多线程的实现方式一:继承Thread类

线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

15_多线程_第3张图片

步骤

  1. 定义一个类继承Thread类
  2. 重写run方法
  3. 创建子类对象
  4. 通过start方法启动线程

eg:


public class Demo {
    public static void main(String[] args) {
        System.out.println("start");

        // 3. 创建子类对象
        MyThread myThread = new MyThread();

        // 4. 通过start方法启动线程
        myThread.start();

        System.out.println("end");
    }
}

        /*
        多线程的实现方式一:继承Thread
         */

// 1. 定义一个类继承Thread类
class MyThread extends Thread {

    // 2. 重写run方法
    @Override
    public void run() {
        // 放的是在线程中要执行的代码
        System.out.println("nihao");
    }
}

15_多线程_第4张图片

:两条路径执行会得到这个结果,如果想endnihao颠倒过来,在输出end语句上面增加一个睡眠TimeUnit.SECONDS.sleep(1);

注意事项

  • 多线程的执行特点是随机的
  • start方法跟run方法有什么区别?
    • start方法才是开辟新的执行路径
    • run方法只是普通方法调用, 并没有开辟新的执行路径, 还是一条执行路径, 仍然是单线程的
  • 同一个线程能否启动多次?
    • 不能启动多次, 会产生java.lang.IllegalThreadStateException的异常报错
  • 谁才代表一个线程?
    • Thread及其子类对象才代表线程, 就是t1,t2

获取和设置线程的名称

获取名称

在这里插入图片描述
在这里插入图片描述

设置名称

在这里插入图片描述


线程的调度方式

给线程分配CPU处理权的过程

调度方式的分类

  • 协同式线程调度
    • 线程的执行时间由线程本身决定, 当这个线程执行完后报告操作系统,切换到下一个线程
  • 抢占式的线程调度方式
    • 线程的执行时间由系统决定, 哪个线程抢到了CPU的执行,哪个线程执行

java中采用哪种调度方式

  • Java中采用的是抢占式的调度方式

线程的优先级

15_多线程_第5张图片

  • MAX_PRIORITY 线程可以具有的最高优先级10
  • MIN_PRIORITY 线程可以具有的最低优先级1
  • NORM_PRIORITY 分配给线程的默认优先级5

在这里插入图片描述

在这里插入图片描述
结论

  • 优先级没有什么用
  • 它仅仅只能被看做是一种"建议"(对操作系统的建议)
    实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)

线程控制API

线程休眠sleep

暂停执行的作用

在这里插入图片描述

TimeUnit.SECONDS.sleep(1);Thread.sleep(1000);是等价的,只不过后者单位是毫秒前者单位是

线程合并join

等待该线程终止。

15_多线程_第6张图片

执行结果上看等待的是子线程, 哪个线程调用了join, 等待的就是这个线程

15_多线程_第7张图片

eg:


public class Demo {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread1 thread1 = new MyThread1();

        // 启动start
        thread1.start();

        // 使用join方法
        try {
            thread1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // main函数打印3个数
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "----" +  i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }


    }

}

class MyThread1 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "----" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

线程礼让yield

在这里插入图片描述
:虽然yield方法使当前线程放弃了CPU的执行权 但是仍然可以参与下轮的CPU的竞争

eg:


public class Demo {
    public static void main(String[] args) {
        YieldThread a = new YieldThread("A");
        YieldThread b = new YieldThread("B");
        a.start();
        b.start();

    }
}

class YieldThread extends Thread{

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

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(this.getName() + "----" + i);
            
            // 暂停当前正在执行的线程对象,并执行其他线程。
            Thread.yield();
        }
    }
}

守护线程setDaemon

线程分类

  • 用户线程(默认)
    • 系统的工作线程
  • 守护线程
    • 为用户线程服务的线程(GC垃圾回收线程),系统的后台线程,可以把它当做用户线程的奴仆。

在这里插入图片描述

  • on - 如果为 true,则将该线程标记为守护线程

注意事项

  • 正在运行的线程都是守护线程时,Java 虚拟机退出
  • 该方法必须在启动线程前调用
    • 如果在start之后写出,则会报错 —> java.lang.IllegalThreadStateException

线程中断stop(已过时)

在这里插入图片描述

该方法具有固有的不安全性

eg:

需求

  • 定义一个flag标记, true 是正常状态 false中断

  • 主线程打印3个数 打印1个 休眠1秒 中断子线程

  • 创建子线程 打印10个数 休眠1秒

  • 打印之前判断一下是否中断 如果正常----> 打印数据

  • 如果发生了中断-------> 不在打印, 并且把中断信息保存到log.txt文件中

  • 格式:年月日 时分秒 哪个线程发生了中断


public class Demo {
    public static void main(String[] args) {

        // 创建线程对象
        ThreadStop threadStop = new ThreadStop();

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

        for (int i = 0; i < 3; i++) {
            System.out.println("main" + "----" + i);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        threadStop.flag = false;

    }
}

class ThreadStop extends Thread {
    // 定义一个标记flag
    boolean flag = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // 判断线程的状态
            if (flag) {
                // 如果正常----> 打印数据
                System.out.println(this.getName() + "----" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                // 创建字符输出流对象
                FileWriter fileWriter = null;
                try {
                    // 如果发生了中断-------> 不在打印, 并且把中断信息保存到log.txt文件中
                    fileWriter = new FileWriter("log.txt");
                    // 创建SimpleDataFormat对象,指定日期格式
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    // write(String s)
                    fileWriter.write(sdf.format(new Date()) + getName() + "发生了中断");
                    fileWriter.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (fileWriter != null) {
                        try {
                            // close 释放资源
                            fileWriter.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}


线程的生命周期

线程的几种状态

理论层面上

新建

  • new出来的线程对象

就绪

  • 线程执行了start()方法后

执行

  • 拥有CPU的执行权

阻塞

  • 线程会处于阻塞状态

死亡

  • run方法执行完

代码层面上

15_多线程_第8张图片

线程状态的转换

15_多线程_第9张图片


多线程实现方式二:实现Runnable接口

15_多线程_第10张图片

步骤

  1. 定义一个类实现Runnable接口
  2. 重写run方法
  3. 创建子类对象
  4. 创建线程对象, 把实现了Runnable接口的子类对象作为参数传递
  5. start方法启动线程

使用的构造方法

15_多线程_第11张图片

15_多线程_第12张图片

eg:


public class Demo {
    public static void main(String[] args) {
         /*
        多线程的实现方式二:实现Runnable接口
         */
         
        // 3. 创建子类对象
        MyRunnable myRunnable = new MyRunnable();

        // 4. 创建线程对象, 把实现了Runnable接口的子类对象作为参数传递
        Thread thread = new Thread(myRunnable);

        // 5. start方法启动线程
        thread.start();

    }
}

// 1. 定义一个类实现Runnable接口
class MyRunnable implements Runnable{

    // 2. 重写run方法
    @Override
    public void run() {
        System.out.println("son of thread is running!");
    }
}

eg:(也可以使用lambda表达式或者匿名内部类)


// 匿名内部类
new Thread(new Runnable() {
    @Override
     public void run() {
         System.out.println("1111");
     }
}).start();

// lambda
new Thread(()->{
    System.out.println("222222");
}).start();

Runnable中的run方法为什么会运行在子线程中?

见下面的伪代码


class Thread{
// 成员变量
	private Runnable  target;
   
    // 构造方法
    Thread(Runnable target){
        init(target);
    }
    void init(){
        // 左边是成员变量 右边是传过来的参数 给成员变量赋值
        this.target = target;
    }
    
    void run(){
        if(target != null){
            target.run()
        }
    }
}


方式一 VS 方式二

  • 步骤上, 方式一4步, 方式二是5
  • 方式一通过继承的方式(单继承的局限性),方式二通过实现接口的方式
  • 方式二把线程跟线程上要做的事情区分开来(执行路径,跟执行路径上的任务区分开来) 解耦
  • 方式二便于数据共享

eg:


public class Demo {
    public static void main(String[] args) {
        MyShare runnable = new MyShare();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);

        thread1.setName("window No.1");
        thread2.setName("window No.2");
        thread3.setName("window No.3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}


class MyShare implements Runnable{
    int ticket = 100;

    @Override
    public void run() {
        while(true){
            if(ticket >0){
				
				// 模拟网络时延
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + "sell No." + (ticket --) + " ticket");
            }
        }
    }
}


多线程数据安全问题

出现的情况

  • 重复情况
    15_多线程_第13张图片
  • 出现不存在的数据
    15_多线程_第14张图片

产生原因

  • 多线程的运行环境(需求)
  • 多线程共享数据(需求)
  • 存在非原子操作(可以进行操作)
    • 原子操作: 一个不可分割的操作(一个操作要么一次执行完, 要么不执行)

解决多线程数据安全问题

synchronized

同步代码块

  • 同步代码块的锁对象(对象 , 用来充当锁的角色)
  • 锁对象可以是任意的java对象, 但是要保证是同一个

语法


synchronized(锁对象){
    // 对共享数据的访问操作
}

修改后为


public class Demo {
    public static void main(String[] args) {
        MyShare runnable = new MyShare();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);

        thread1.setName("window No.1");
        thread2.setName("window No.2");
        thread3.setName("window No.3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}


class MyShare implements Runnable {
    int ticket = 100;

    // 定义一把锁
    Object object = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (object) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "sell No." + (ticket--) + " ticket");
                }
            }
        }
    }
}

同步方法

同步方法的锁对象是:this

eg:


    private synchronized void sell() {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "sell No." + (ticket--) + " ticket");
        }
    }

静态同步方法

静态同步方法的锁对象是:字节码文件对象(Class对象)
字节码文件对象的获取方法: 对象.getClass()或者类名.class

eg:


    private static synchronized void sell() {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "sell No." + (ticket--) + " ticket");
        }
    }
    }

// 上面的同步代码块是
synchronized (MyShare.getClass()); // MyShare是创建的对象

synchronized的细节问题

  • 执行流程
    • A、B 2个线程访问synchronized代码块中的内容
    • 假设A线程抢到了CPU的执行权, 看一下锁对象是否可用,如果可用, A线程就持有了锁对象, A线程访问同步代码块的内容
    • A还没有访问结束,发生了线程切换,B抢到了执行权,B也想访问同步代码块中的内容, 看一下锁是否可用, 不可用, 对于B线程来说, 只能在synchronized外面等待, B就处于同步阻塞状态
    • A再次抢到执行权. A接着执行,访问结束, 退出synchronized代码块, A释放锁
    • B线程就可以获取锁, 访问synchronized代码块中的内容.

eg:

public class Demo {

    // 定义一把锁
    public static final Object obj = new Object();

    public static void main(String[] args) {
        // 定义线程A
        new Thread(() -> {
            synchronized (obj) {
                System.out.println("进入线程A");

                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("退出线程A");
            }
        },"A").start();

        // main休眠1s
        try {
            System.out.println("main函数休眠1s");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 定义线程B
        new Thread(() -> {
            synchronized (obj) {
                System.out.println("进入线程B");

                try {
                    TimeUnit.SECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("退出线程B");
            }
        },"B").start();
    }
}

出现异常就会释放锁

eg:


public class Demo {

    // 定义一把锁
    public static final Object OBJECT = new Object();
    
    public static int count = 0;

    public static void main(String[] args) {
        // 定义线程A
        new Thread(() -> {
            System.out.println("进入线程A");
            synchronized (OBJECT) {
                while (true) {
                    count++;
                    System.out.println("A进入到synchronized中");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(count == 5){
                        System.out.println("count = " + count);
                        // 人为制造一个异常
                        System.out.println(10/0);
                    }
                }
            }
        }, "A").start();

        // main休眠1s
        try {
            System.out.println("main函数休眠1s");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 定义线程B
        new Thread(() -> {
            System.out.println("进入线程B");
            synchronized (OBJECT) {
                System.out.println("B进入到了synchronized中");

            }
            System.out.println("退出线程B");
        }, "B").start();
    }
}

  • 2条字节码指令(monitorenter/monitorexit)

Lock锁

它是一个接口

15_多线程_第15张图片

方法

  • 在这里插入图片描述

  • 在这里插入图片描述

ReentrantLock可重入锁

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

eg:


public class Demo {
    public static void main(String[] args) {
        MyShare1 runnable = new MyShare1();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);

        thread1.setName("window No.1");
        thread2.setName("window No.2");
        thread3.setName("window No.3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}


class MyShare1 implements Runnable {
    int ticket = 100;

    //定义一把Lock锁
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 获取锁
            // lock()
            lock.lock();
            try{
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "sell No." + (ticket--) + " ticket");
                }
            }finally {
                // 释放锁
                // unlock()
                lock.unlock();
            }
        }
    }
}

synchronized VS Lock

  • synchronized关键字Lock是个接口
  • synchronized是一把隐式的锁, 加锁和释放锁是由jvm自动完成的Lock它是一把真正的(显式的)锁, 我们能看到加锁跟释放锁的过程(lock , unlock)

死锁

2个或以上线程因为争抢资源而造成的互相等待的现象

发生的场景:一般出现在同步代码块嵌套

语法

synchronized(objA){
    synchronized(objB){   
}
}

eg:


/*
模拟死锁产生的场景
 */

public class Demo {
    public static void main(String[] args) {
        new Thread(new DieLock(true)).start();
        new Thread(new DieLock(false)).start();
    }
}

// 定义一个锁类
class MyLock {

    public static final Object OBJECTA = new Object();
    public static final Object OBJECTB = new Object();
}

class DieLock implements Runnable {
    // 定义一个flag
    boolean flag = true;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (MyLock.OBJECTA) {
                System.out.println("if A");
                synchronized (MyLock.OBJECTB) {
                    System.out.println("if B");
                }
            }
        } else {
            synchronized (MyLock.OBJECTB) {
                System.out.println("else A");
                synchronized (MyLock.OBJECTA) {
                    System.out.println("else B");
                }
            }
        }
    }
}

解决死锁的办法

  • 使加锁的顺序保持一致

eg:

    @Override
    public void run() {
        if (flag) {
            synchronized (MyLock.OBJECTA) {
                System.out.println("if A");
                synchronized (MyLock.OBJECTB) {
                    System.out.println("if B");
                }
            }
        } else {
            synchronized (MyLock.OBJECTA) {
                System.out.println("else A");
                synchronized (MyLock.OBJECTB) {
                    System.out.println("else B");
                }
            }
        }
    }
  • 再加一把锁,使其变成原子操作

eg:


    @Override
    public void run() {
        if (flag) {
            synchronized (MyLock.OBJECT) {
                synchronized (MyLock.OBJECTA) {
                    System.out.println("if A");
                    synchronized (MyLock.OBJECTB) {
                        System.out.println("if B");
                    }
                }
            }
        } else {
            synchronized (MyLock.OBJECT) {
                synchronized (MyLock.OBJECTB) {
                    System.out.println("else A");
                    synchronized (MyLock.OBJECTA) {
                        System.out.println("else B");
                    }
                }
            }
        }
    }

生产者消费者模型

15_多线程_第16张图片

同步代码块版本


// 蒸笼类
/*
使用同步代码块
 */

// 蒸笼类
public class Box {

    // 定义成员
    Food food;

    // 生产包子的方法 只要生产者会执行
    public void makeFood(Food newFood){
        food = newFood;
        System.out.println(Thread.currentThread().getName()
                + "生产了" + food);
    }

    // 吃包子的方法 只要消费者会执行
    public void eatFood(){
        System.out.println(Thread.currentThread().getName()
                + "吃了" + food);
        food = null;
    }

    // 判断蒸笼状态的方法
    public boolean isEmpty(){
        return food == null;
    }

}

// 定义包子类
class Food{
    String name;
    int price;

    public Food(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Food{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}


// 生产者任务
public class ProducerTask implements Runnable{
    // 定义一个成员
    Box box;

    Food[] foods = {new Food("pork",3),
            new Food("mutton",4),
            new Food("tomato",2)};

    Random random = new Random();

    public ProducerTask(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        // 生产包子
        while (true){
            // 使用synchronized
            synchronized (box){
                // 按照上图逻辑
                if(box.isEmpty()){
                    // 如果蒸笼为空,没有包子生产包子 放进去
                    int index = random.nextInt(foods.length);
                    box.makeFood(foods[index]);
                    box.notify();
                }else{
                    try {
                        box.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}


// 消费者任务
public class ConsumerTask implements Runnable{
    Box box;

    public ConsumerTask(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        // 吃包子
        while (true){
            synchronized (box){
                if(box.isEmpty()){
                    try {
                        box.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    box.eatFood();
                    box.notify();
                }
            }
        }
    }
}

// 运行
public class Demo {
    public static void main(String[] args) {
        // 创建一个Box对象
        Box box = new Box();

        // 创建生产者任务
        ProducerTask producerTask = new ProducerTask(box);

        // 创建消费者任务
        ConsumerTask consumerTask = new ConsumerTask(box);

        // 创建生产者线程
        Thread t1 = new Thread(producerTask,"生产者线程");

        // 创建消费者线程
        Thread t2 = new Thread(consumerTask,"消费者线程");

        // start启动线程
        t1.start();
        t2.start();

    }
}

同步方法版本


/*
使用同步方法
 */

// 蒸笼类
public class Box {

    // 定义成员
    Food food;

    // 生产包子的方法 只要生产者会执行
    public synchronized void makeFood(Food newFood){
        if (food == null){
            food = newFood;
            System.out.println(Thread.currentThread().getName()
                    + "生产了" + food);
            this.notify();
        }else{
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 吃包子的方法 只要消费者会执行
    public synchronized void eatFood(){
        if(food == null){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()
                    + "吃了" + food);
            food = null;
            this.notify();
        }

    }
}

// 定义包子类
class Food{
    String name;
    int price;

    public Food(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Food{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}


// 生产者任务
public class ProducerTask implements Runnable {
    // 定义一个成员
    Box box;

    Food[] foods = {new Food("pork", 3),
            new Food("mutton", 4),
            new Food("tomato", 2)};

    Random random = new Random();

    public ProducerTask(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        // 生产包子
        while (true) {
            // 按照上图逻辑
                // 如果蒸笼为空,没有包子生产包子 放进去
                int index = random.nextInt(foods.length);
                box.makeFood(foods[index]);
        }
    }
}


// 消费者任务
public class ConsumerTask implements Runnable {

    Box box;

    public ConsumerTask(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        // 吃包子
        while (true) {
            box.eatFood();
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        // 创建一个Box对象
        Box box = new Box();

        // 创建生产者任务
        ProducerTask producerTask = new ProducerTask(box);

        // 创建消费者任务
        ConsumerTask consumerTask = new ConsumerTask(box);

        // 创建生产者线程
        Thread t1 = new Thread(producerTask,"生产者线程");

        // 创建消费者线程
        Thread t2 = new Thread(consumerTask,"消费者线程");

        // start启动线程
        t1.start();
        t2.start();

    }
}

当有多个生产 多个消费者的时候, 出现"卡顿"的现象, 怎么解决?

答:使用notifyAll


线程间通信

wait与notify机制

  • 在这里插入图片描述
  • 15_多线程_第17张图片

作用

  • 拥有相同锁的线程才可以实现wait/notify机制
  • wait()方法使线程暂停运行,而notify() 方法通知暂停的线程继续运行
  • wait会释放锁,notify不会释放锁

wait()

  • 阻塞功能
    • 当在某线程中,对象上.wait(), 在哪个线程中调用wait(), 导致哪个线程处于阻塞状态
    • 当某线程,因为调用执行某对象的wait(),而处于阻塞状态,我们说,该线程在该对象上阻塞。
  • 唤醒条件
    • 当某线程,因为某对象A的wait(), 而处于阻塞状态时,如果要唤醒该线程,只能在其他线程中
      再同一个对象(即对象A)上调用其notify()或notifyAll()
    • 在线程的阻塞对象上,调用notify或notifyAll方法,才能唤醒,在该对象上阻塞的线程
  • 运行条件
    • 当前线程必须拥有此对象监视器
      • 监视器:指synchronized代码块中的锁对象
    • 即我们只能在,当前线程所持有的synchronized代码块中的,锁对象上调用wait方法,
      才能正常执行
    • 如果没有锁对象就会有这样一个异常IllegalMonitorStateException
  • 执行特征
    • a.该线程发布(release)对此监视器的所有权
    • b.等待(阻塞)
    • 注意:Thread的sleep方法,执行的时候:该线程不丢失任何监视器的所属权

wait方法的使用条件:必须有synchronized,在锁对象上调用wait

wait方法的执行特征使当前线程暂停执行,处于阻塞状态,释放锁

eg:


public class Demo {

    // 定义一把锁
    public static final Object OBJECT = new Object();
    public static void main(String[] args) {

        // 创建并启动一个线程
        new Thread(()->{
            System.out.println("A is running");
            synchronized (OBJECT){
                System.out.println("进入A的同步代码块");
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 使用wait方法
                try {
                    System.out.println("wait before");
                    OBJECT.wait();
                    System.out.println("wait after");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();


        // main休眠
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        // 创建并启动一个线程
        new Thread(()->{
            System.out.println("B is running");
            synchronized (OBJECT){
                System.out.println("进入B的同步代码块");
                // 执行notify方法
                System.out.println("notify before");
                OBJECT.notify();
                System.out.println("notify after");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

    }
}

15_多线程_第18张图片

notify()

  • 唤醒在此对象监视器上等待的单个线程
  • 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程
  • 选择是任意性的

notifyAll()

唤醒多个等待的线程

为什么wait,notify,notifyAll方法不定义在Thread类中?

任意Java对象都能充当锁的角色

你可能感兴趣的:(JavaSE,java)