锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. Java多线线程-----等待唤醒机制(wait notify)

Java多线线程-----等待唤醒机制(wait notify)

0
  • JAVA
  • 发布于 2024-08-02
  • 0 次阅读
黄健
黄健

原文链接:https://blog.csdn.net/2302_79862386/article/details/140698001

一.等待唤醒机制简介:

由于线程之间是抢占式执行的,因此线程的执行顺序难以预知。但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。为了完成协调的工作,这里主要设计三个方法:

wait() / wait(long timeout) : 让当前线程进入等待状态

notify() / notifyAll(): 唤醒在当前对象上等待的线程

注意:wait,notify,notifyAll都是Object类的方法

二.synchronized,wait(),notify():

synchronized 的含义:

Java中每一个对象都可以成为一个监视器(Monitor), 该Monitor由一个锁(lock), 一个等待队列(waiting queue ), 一个入口队列( entry queue).

对于一个对象的方法, 如果没有synchronized关键字, 该方法可以被任意数量的线程,在任意时刻调用。

对于添加了synchronized关键字的方法,任意时刻只能被唯一的一个获得了对象实例锁的线程调用。

synchronized用于实现多线程的同步操作

wait()功能:

wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步

wait()总是在一个循;环中被调用,挂起当前线程来等待一个条件的成立。 Wait调用会一直等到其他线程调用notifyAll()时才返回。

当一个线程在执行synchronized 的方法内部,调用了wait()后, 该线程会释放该对象的锁, 然后该线程会被添加到该对象的等待队列中(waiting queue), 只要该线程在等待队列中, 就会一直处于闲置状态, 不会被调度执行。 要注意wait()方法会强迫线程先进行释放锁操作,所以在调用wait()时, 该线程必须已经获得锁,否则会抛出异常。由于wait()在synchonized的方法内部被执行, 锁一定已经获得, 就不会抛出异常了。

notify()的功能:

wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于线程同步

当一个线程调用一个对象的notify()方法时, 调度器会从所有处于该对象等待队列(waiting queue)的线程中取出任意一个线程, 将其添加到入口队列( entry queue) 中. 然后在入口队列中的多个线程就会竞争对象的锁, 得到锁的线程就可以继续执行。 如果等待队列中(waiting queue)没有线程, notify()方法不会产生任何作用

notifyAll() 和notify()工作机制一样, 区别在于notifyAll()会将等待队列(waiting queue)中所有的线程都添加到入口队列中(entry queue)

三.等待唤醒机制案例:

1.让t1执行wait()方法。

2.此时t2得到锁,再让t2执行notify()方法释放锁。

3.此时t1得到锁,t1会自动从wait()方法之后的代码,继续执行。

4.通过上述流程,我们就可以清楚的看到,wait()和notify()各自是怎么工作的了,也可以知道两者是怎么配合的了。

import java.util.*;

public class Main {

    //创建一个将被两个线程同时访问的共享对象

    public static Object loker = new Object();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{

            synchronized (loker){

                System.out.println("线程一初次获得对象锁,执行过程中调用锁对象的wait()方法~~");

                try {

                    loker.wait();

                    System.out.println("当线程一被唤醒后,后面的代码继续执行");

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            System.out.println("线程一运行结束");

        },"线程一");

 

        Thread t2 = new Thread(()->{

            synchronized (loker){

                System.out.println("线程二初次获得对象锁,执行过程中调用锁对象的notify()方法~~");

                loker.notify();

                System.out.println("唤醒线程一前,后面的代码继续执行~~");

            }

            System.out.println("线程二结束");

        },"线程二");

 

        t1.start();

        //防止t2优先获得CPU执行权而错过唤醒t1

        Thread.sleep(1000);

        t2.start();

    }

}

运行结果(运行流程也就是运行的打印结果):

例题一:

有三个线程,线程名称分别为:a,b,c。每个线程打印自己的名称。

需要让他们同时启动,并按 c,b,a的顺序打印

代码详解:

import java.util.*;

public class Test {

    public static Object loker1 = new Object();

    public static Object loker2 = new Object();

 

    public static void main(String[] args) {

        System.out.println("打印顺序如下:");

 

        Thread t1 = new Thread(()->{

            //为了防止线程A的唤醒没有被线程B接受,这里先让线程A睡一会

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

 

            System.out.println(Thread.currentThread().getName()+": C");

            synchronized (loker1){

                loker1.notify();

            }

        },"线程C");

 

        Thread t2 = new Thread(()->{

            synchronized (loker1){

                //等待线程A的唤醒

                try {

                    loker1.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            System.out.println(Thread.currentThread().getName()+": B");

            //线程B唤醒线程C

            synchronized (loker2){

                loker2.notify();

            }

        },"线程B");

 

        Thread t3 = new Thread(()->{

            //线程C等待线程A的唤醒

            synchronized (loker2){

                try {

                    loker2.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            System.out.println(Thread.currentThread().getName()+": A");

        },"线程A");

        //开启线程

        t1.start();

        t2.start();

        t3.start();

    }

}

运行结果:

例题二:

有三个线程,分别只能打印A,B和C

要求按顺序打印ABC,打印10次

输出示例:

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

大致过程:

代码详解:

import java.util.*;

public class Demo {

    private static Object locker1 = new Object();

    private static Object locker2 = new Object();

    private static Object locker3 = new Object();

 

    public static void main(String[] args) throws InterruptedException {

        System.out.println("打印结果:");

        Thread t1 = new Thread(() -> {

            try {

                for (int i = 0; i < 10; i++) {

                    synchronized (locker1) {

                        locker1.wait();

                    }

                    System.out.print("A");

                    synchronized (locker2) {

                        locker2.notify();

                    }

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        });

 

        Thread t2 = new Thread(() -> {

            try {

                for (int i = 0; i < 10; i++) {

                    synchronized (locker2) {

                        locker2.wait();

                    }

                    System.out.print("B");

                    synchronized (locker3) {

                        locker3.notify();

                    }

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        });

 

        Thread t3 = new Thread(() -> {

            try {

                for (int i = 0; i < 10; i++) {

                    synchronized (locker3) {

                        locker3.wait();

                    }

                    System.out.println("C");

                    synchronized (locker1) {

                        locker1.notify();

                    }

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        });

        t1.start();

        t2.start();

        t3.start();

        //让三个线程都拿到锁

        Thread.sleep(1000);

 

        // 从线程 t1 启动

        synchronized (locker1) {

            locker1.notify();

        }

    }

}

运行结果:

四.什么时候释放锁—wait()、notify()

由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的。在以下情况下,持有锁的线程会释放锁:

1.执行完同步代码块。

2.在执行同步代码块的过程中,遇到异常而导致线程终止。

3.在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。

除了以上情况外,只要持有锁的对象还没有执行完同步代码块,就不会释放锁。因此在以下情况下,线程不会释放锁:

1.在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。

2.在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。

3.在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。但Thread类的suspend()方法已经被废弃。

标签: #JAVA 991
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.