Java多线程技术二:线程间通信——wait/notify机制

1 概述

        线程时操作系统中独立的个体,但这些个体如果不经过特殊的处理是不能成为一个整体的。线程间的通信就是使线程成为整体的比用方案之一,可以说,是线程间进行通信后系统之间的交互性会更强大,CPU利用率会得以大幅提高,同时程序员在处理的过程中可以有效把控与监督各线程任务。

2 不使用wait/notify机制进行通信的缺点

public class MyList {volatile private List list = new ArrayList<>();public void add(){list.add("jay chou");}public int size(){return list.size();}}

  

public class ThreadA extends Thread{private MyList list;public ThreadA(MyList list) {this.list = list;}@Overridepublic void run(){try {for (int i = 0; i < 10; i++) {list.add();System.out.println("添加了" + (i + 1) +"个元素");Thread.sleep(1000);}}catch (InterruptedException e){e.printStackTrace();}}
}
public class ThreadB extends Thread{private MyList list;public ThreadB(MyList list) {this.list = list;}@Overridepublic void run(){try {while(true){if(list.size() == 5){System.out.println(" == 5了,线程b要退出了");throw new InterruptedException();}}}catch (InterruptedException e){e.printStackTrace();}}
}
public class Run1 {public static void main(String[] args) {MyList list = new MyList();ThreadA a = new ThreadA(list);a.setName("a");a.start();ThreadB b = new ThreadB(list);b.setName("b");b.start();}
}

      

        虽然两个线程之间实现了通信,但还存在缺点:线程ThreadB不停地通过while语句轮询机制来检测某一个条件,这样会浪费CPU资源。如果轮询的时间间隔很短,更浪费CPU资源;如果轮询的时间很长,有可能会取不到想要得到的数据。

3 什么是wait/notify机制

         wait/notify(等待/通知)机制在生活中很常见,比如在就餐时就会出现,如下图:

        厨师和服务员在”菜品传递台“上交互,在这期间会想到几个问题:

        1、厨师做完一个菜的时间未知,所以厨师把菜品放到”菜品传递台“上的时间也未知。

        2、服务员取到菜的时间取决于厨师,所以服务器就有”wait“的状态。

        3、服务器如何能取到菜?这又要取决于厨师。厨师将菜放到”菜品传递台“上,其实就相当于一个notify,这时服务员才可以拿到菜并交给就餐者。

        4、这个过程中就出现了”wait/notify“机制。

        需要说明的是,前文中多个线程之间也可以实现通信,就是就是多个线程共同访问同一个变量。但那种通信机制却不是”等待/通知“,两个线程完全是主动操作同一个共享变量。 但那种通信机制却不失“等待/通知”,两个线程完全是主动操作同一个共享变量,在花费读取时间的基础上,读到的值并不确定是不是想要的。

4 wait/notify机制的原理

        注意:拥有相同锁的线程才可以实现wait/notify机制。所以下文都是假定操作同一个锁。

        wait()是Object类的方法,它的作用是使当前执行wait()方法的线程进行等待,在wait()所在的代码行处暂停执行,并释放锁,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。通过通知机制是某个线程继续执行wait()方法后面的代码时,对线程的选择是按照执行wait()方法的顺序确定的,并需要重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException。它是RuntimeException的一个子类,因此不需要try-catch语句进行捕获异常。

        notify()也要在同步方法或同步块中调用,即在调用前线程必须要获得锁,如果调用notify()没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知哪些可能等待该锁的其他线程,如果有多个线程等待,则按照执行wait()方法的顺序对呈等待状态的线程发出1次通知,并使那个线程重新获取锁。需要说明的是,执行notify()方法后,当前线程不会马上释放锁,呈等待状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步区域后,当前线程才会释放锁,而呈等待状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的等待线程运行完毕后,它会释放掉该对象锁。此时如果没有再次使用notify语句,那么其他等待状态的线程会因为没有得到通知而继续等待。

        wait和notify是Object类中的方法。总结来说:wait是线程暂停运行,而notify通知暂停的线程继续运行。

5 wait()的基本用法

        wait()的作用是使当前线程暂停运行,并释放锁。   

public class Test1 {public static void main(String[] args) {try {String s = new String("");s.wait();}catch (InterruptedException e){e.printStackTrace();}}
}

        出现异常的原因是没有“对象监视器”,也就是没有锁。

public class Test2 {public static void main(String[] args) {try {String lock = new String();System.out.println("synchronzied之前");synchronized (lock){System.out.println("进入synchronzied代码块");lock.wait();System.out.println("执行完wait()方法");}System.out.println("执行完synchronzied代码块的代码");}catch (InterruptedException e){e.printStackTrace();}}
}

 

        此时线程已经开始等待,但不能永远等待下去,否则程序就不会继续向下运行了。使用notify()方法可以是等待状态的线程继续运行。

6 实现wait/notify机制

public class MyThread1 extends Thread{private Object lock;public MyThread1(Object lock) {this.lock = lock;}@Overridepublic void run(){try {synchronized (lock){System.out.println("开始 等待时间 = " + Utils.data(System.currentTimeMillis()));lock.wait();System.out.println("结束 等待时间 = " + Utils.data(System.currentTimeMillis()));}}catch (InterruptedException e){e.printStackTrace();}}
}
public class MyThread2 extends Thread{private Object lock;public MyThread2(Object lock) {this.lock = lock;}@Overridepublic void run(){synchronized (lock){System.out.println("开始 通知时间 = " + Utils.data(System.currentTimeMillis()));lock.notify();System.out.println("结束 通知时间 = " + Utils.data(System.currentTimeMillis()));}}
}
public class Run1 {public static void main(String[] args) {try {Object object = new Object();MyThread1 t1 = new MyThread1(object);t1.start();Thread.sleep(3000);MyThread2 t2 = new MyThread2(object);t2.start();}catch (InterruptedException e){e.printStackTrace();}}
}

        从运行结果来看,3秒后线程被通知唤醒。

7 使用wait/notify实现线程销毁

        使用wait/notify可以在某个特定条件下,销毁线程。

public class MyList {private static List list= new ArrayList<>();public static void add(){list.add("1");}public static int size(){return list.size();}
}

public class ThreadA extends Thread{private Object lock;public ThreadA(Object lock) {this.lock = lock;}@Overridepublic void run(){try {synchronized (lock){if(MyList.size() != 5){System.out.println("开始等待 = " + Utils.data(System.currentTimeMillis()));lock.wait();System.out.println("结束等待 = " + Utils.data(System.currentTimeMillis()));}}}catch (InterruptedException e){e.printStackTrace();}}
}
public class ThreadB extends Thread{private Object lock;public ThreadB(Object lock) {this.lock = lock;}@Overridepublic void run(){try {synchronized (lock){for (int i = 0; i < 10; i++) {MyList.add();if(MyList.size() == 5){lock.notify();System.out.println("已发出通知");}System.out.println("添加了 " + (i+1));Thread.sleep(1000);}}}catch (InterruptedException e){e.printStackTrace();}}
}
public class Run1 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();ThreadA t1 = new ThreadA(lock);t1.start();Thread.sleep(50);ThreadB t2 = new ThreadB(lock);t2.start();}
}

        日志信息“结束等待”在最后输出,这也说明notify()方法执行后并不立即释放锁,这点后面会补充介绍。

        关键字synchronzied可以将任何一个Object作为锁来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁,而notify操作可以唤醒一个由于调用了wait操作而处于wait状态中的线程,使其进入就绪状态,被重新唤醒的线程会试图重新获取临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于wait状态中的线程,那么该命令就会被忽略。

        notify()方法按照执行wait方法的顺序唤醒等待同一个锁的“一个”线程,进入可运行状态。也就是notify()方法仅通知“一个”线程。而notifyAll()方法执行后,会按照执行 wait()方法的倒序依次唤醒全部的线程。

8 对业务代码进行凤封装

        前面的代码是在自定义的类中处理业务,而业务代码要尽量放在Service类中进行处理。

public class MyList {volatile private List list = new ArrayList<>();public void add(){list.add("q");}public int size(){return list.size();}
}

public class MyService {private Object lock = new Object();private MyList list = new MyList();public void watiMethod(){try {synchronized (lock){if(list.size() != 5){System.out.println("开始等待 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());lock.wait();System.out.println("结束等待 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());}}}catch (InterruptedException e){e.printStackTrace();}}public void notifyMethod(){try {synchronized (lock){System.out.println("开始通知 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());for (int i = 0; i < 10; i++) {list.add();if(list.size() == 5){lock.notify();System.out.println("发出通知,wait后面的代码还没有立即执行,因为锁没有释放");}System.out.println("add次数 " + (i+1));Thread.sleep(1000);}System.out.println("结束通知 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());}}catch (InterruptedException e){e.printStackTrace();}}
}
public class ThreadA extends Thread{private MyService service;public ThreadA(MyService service) {this.service = service;}@Overridepublic void run(){service.watiMethod();}
}
public class ThreadB extends Thread{private MyService service;public ThreadB(MyService service) {this.service = service;}@Overridepublic void run(){service.notifyMethod();}
}
public class Run1 {public static void main(String[] args) throws InterruptedException {MyService service = new MyService();ThreadA t1 = new ThreadA(service);t1.start();Thread.sleep(3000);ThreadB t2 = new ThreadB(service);t2.start();}
}

9 线程状态的切换 

        前面介绍了与Thread有关的大部分API,这些API可以改变线程对象的状态。

        1)创建了一个新的线程对象后,再调用它的start方法,系统会为此线程分配CPU资源,处于可运行状态,是一个准备运行的阶段。如果线程抢占到CPU资源,此线程就处于运行状态。

        2)可运行状态和运行状态可互相切换,因为有可能线程运行一段时间后其他高优先级的线程抢占了CPU资源,这时此线程就从运行状态变成可运行状态。

        线程进入可运行状态可分为以下四种情况:

        1、调用sleep()方法后经过的时间超过了指定的休眠时间;

        2、线程成功获得了试图同步的监视器;

        3、线程正在等待某个通知,其他线程发出了通知;

        4、处于挂起状态的线程调用了resume方法。

        3)暂停状态结束后,线程进入可运行状态,等待系统重新分配资源。

            出现阻塞的情况可分为以下5种:

                1、线程调用sleep方法,主动放弃占用的处理器资源;

                2、线程调用了阻塞式I/O方法,在该方法返回前,该线程被阻塞;

                3、线程试图获得一个同步监视器,但该监视器正被其他线程所持有;

                4、线程等待某个通知;

                5、程序调用了suspend方法将该线程挂起。此方法容易导致死锁,应尽量避免使用。

        4)run方法运行结束后进入销毁阶段,整个线程执行完毕。

10 wait()方法导致锁立即释放

        wait()被执行后,锁会被立即释放,但执行完notify方法后,锁不会立即释放。

public class Service {public void testMethod(Object lock){try {synchronized (lock){System.out.println("开始等待:");lock.wait();System.out.println("结束等待");}}catch (InterruptedException e){e.printStackTrace();}}
}
public class ThreadA extends Thread{private Object lock;public ThreadA(Object lock) {this.lock = lock;}@Overridepublic void run(){Service service = new Service();service.testMethod(lock);}
}
public class ThreadB extends Thread{private Object lock;public ThreadB(Object lock) {this.lock = lock;}@Overridepublic void  run(){Service service = new Service();service.testMethod(lock);}
}

public class Run1 {public static void main(String[] args) {Object locl = new Object();ThreadA t1 = new ThreadA(locl);t1.start();ThreadB t2 = new ThreadB(locl);t2.start();}
}

              

11 sleep()方法不释放锁

        如果将wait方法改成sleep方法,就成了同步效果。

public class Service {public void testMethod(Object lock){try {synchronized (lock){System.out.println("开始等待:");Thread.sleep(4000);System.out.println("结束等待");}}catch (InterruptedException e){e.printStackTrace();}}
}

 

12 notify方法不立即释放锁

        还有一个结论要进行实验:方法notify被执行后,不立即释放锁。

public class MyService {private Object lock = new Object();public void testMethod(){try {synchronized (lock){System.out.println("开始等待时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());lock.wait();System.out.println("结束等待时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());}}catch (InterruptedException e){e.printStackTrace();}}public void testNofity(){try {synchronized (lock){System.out.println("开始通知时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());lock.notify();Thread.sleep(4000);System.out.println("结束通知时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());}}catch (InterruptedException e){e.printStackTrace();}}
}

     

public class ThreadA extends Thread{private MyService service;public ThreadA(MyService service) {this.service = service;}@Overridepublic void run(){service.testMethod();}
}

     

public class ThreadB extends Thread{private MyService service;public ThreadB(MyService service) {this.service = service;}@Overridepublic void  run(){service.testNofity();}
}

   

public class Run1 {public static void main(String[] args) throws InterruptedException {MyService service = new MyService();ThreadA t1 = new ThreadA(service);t1.start();Thread.sleep(500);ThreadB t2 = new ThreadB(service);t2.start();}
}

通过控制台打印的时间来分析,得到的结论:必须执行完notify方法所在的同步代码块后,才释放锁。

13 notify方法只通知一个线程

        每次调用notify方法时,只通知一个线程进行唤醒,唤醒的顺序按执行wait方法的正序。

public class MyService {private Object lock = new Object();public void waitMethod(){try {synchronized (lock){System.out.println("开始等待时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());lock.wait();System.out.println("结束等待时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());}}catch (InterruptedException e){e.printStackTrace();}}public void notifyMethod(){synchronized (lock){System.out.println("开始唤醒时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());lock.notify();System.out.println("结束唤醒时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());}}
}
public class ThreadA extends Thread{private MyService service;public ThreadA(MyService service) {this.service = service;}@Overridepublic void run(){service.waitMethod();}
}
public class ThreadB extends Thread{private MyService service;public ThreadB(MyService service) {this.service = service;}@Overridepublic void run(){service.notifyMethod();}
}
public class Run1 {public static void main(String[] args) throws InterruptedException {MyService service = new MyService();for (int i = 0; i < 10; i++) {ThreadA t1 = new ThreadA(service);t1.start();}Thread.sleep(1000);ThreadB t1 = new ThreadB(service);t1.start();Thread.sleep(500);ThreadB t2 = new ThreadB(service);t2.start();Thread.sleep(500);ThreadB t3 = new ThreadB(service);t3.start();Thread.sleep(500);ThreadB t4 = new ThreadB(service);t4.start();Thread.sleep(500);ThreadB t5 = new ThreadB(service);t5.start();}

        通过以上几个实验,得出以下三个结论:
        1、执行完notify方法后,按照执行wait的顺序唤醒其他线程。notify所在的同步到代码块执行完才会释放对象的锁,其他线程继续执行wait之后的代码。

        2、在执行同步代码块的过程中,遇到异常而导致县城终止时,锁也会被释放。

        3、在执行同步代码块的过程中执行了锁所属对象的wait方法,这个线程会释放对象锁,等待被唤醒。

14 notifyAll方法通知所有线程

        前面示例中通过多次调用notify方法来实现5个线程被唤醒,当并不能保证实际系统中仅有5个线程,就是notify方法的调用次数小于线程对象的数量,那么会出现部分线程对象没有被唤醒的情况。为了唤醒全部线程,可以使用notifyAll方法。 

       注意,notifyAll方法会按照执行wait方法的倒序依次对其他线程进行唤醒。对上一节的MyService.java进行修改,把notifyMethod()方法中的notify()改成notifyAll(),然后再新建Run2.java类。

public class Run2 {public static void main(String[] args) throws InterruptedException {MyService service = new MyService();for (int i = 0; i < 10; i++) {ThreadA t1 = new ThreadA(service);t1.start();}Thread.sleep(1000);ThreadB t2 = new ThreadB(service);t2.start();}
}

        通过运行结果看到,唤醒的顺序是调用wait()方法的倒序。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/200004.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

执法记录仪、一体化布控球等目前支持的AI智能算法、视频智能分析算法有哪些

一、前端设备实现AI算法 主要是基于安卓的布控球实现&#xff0c;已有的算法包括&#xff1a; 1&#xff09;人脸&#xff1b;2&#xff09;车牌&#xff1b;3&#xff09;是否佩戴安全帽&#xff1b;4&#xff09;是否穿着工装&#xff1b; 可以支持定制开发 烟雾&#xf…

使用晶振遇到的两个问题

并联电阻的问题 在一些方案中&#xff0c;晶振并联1MΩ电阻时&#xff0c;程序运行正常&#xff0c;而在没有1MΩ电阻的情况下&#xff0c;程序运行有滞后及无法运行现象发生。 原因分析&#xff1a; 在无源晶振应用方案中&#xff0c;两个外接电容能够微调晶振产生的时钟频率…

开放式蓝牙耳机什么品牌好?南卡、韶音、cleer开放式耳机哪个好?

开放式耳机采用不入耳的设计&#xff0c;提供更为舒适的佩戴体验&#xff0c;不会给耳朵带来持续的压力和损害&#xff0c;减轻身体负担。同时&#xff0c;由于无需将耳机插入耳朵内&#xff0c;减少了细菌滋生的可能性&#xff0c;避免了一些耳道健康问题。这些优点也是开放式…

P5 Linux 标准C库函数

目录 前言 01 标准输入、标准输出和标准错误 02 打开文件 fopen() 03 新建文件的权限 04 fclose()关闭文件 05 读文件和写文件 06 库函数 fseek 定位 6.1 lseek的使用 07 ftell()函数 前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_Chen…

Dinky之安装部署与基本使用

Dinky之安装部署与基本使用 Dinky概览Linux安装部署解压到指定目录初始化MySQL数据库修改配置文件加载依赖启动Dinky Docker部署启动dinky-mysql-server镜像启动dinky-standalone-server镜像 Dinky的基本使用上传jar包Flink配置集群管理集群实例管理集群配置管理 创建作业语句编…

打破界限:SQL数据库水平扩展的8大挑战与机遇

数据库扩展是指提升数据库处理更多数据、更多用户或更多交易的能力。通常&#xff0c;SQL数据库采用垂直扩展的方式&#xff0c;即通过增加更多的CPU、内存或存储空间来增强数据库服务器的性能。然而&#xff0c;这种方法受限于单个服务器的硬件能力。 为了克服这一限制&#…

《形式语言与自动机理论(第4版)》笔记(二)

文章目录 [toc]前导《形式语言与自动机理论&#xff08;第4版&#xff09;》笔记&#xff08;一&#xff09; 第三章&#xff1a;有穷状态自动机3.1|语言的识别3.2|有穷状态自动机即时描述 s e t ( ) set() set()例题问题 1 1 1解答问题 2 2 2解答 3.3|不确定的有穷状态自动机构…

pandas详细笔记

一&#xff1a;什么是Pandas from matplotlib import pyplot import numpy as np import pandas as pdarange np.arange(1, 10, 2) series pd.Series(arange,indexlist("ABCDE")) print(series)二&#xff1a;索引 三&#xff1a;切片 位置索引切片&#xff08;左闭…

【数据结构(七)】查找算法

文章目录 查找算法介绍1. 线性查找算法2. 二分查找算法2.1. 思路分析2.2. 代码实现2.3. 功能拓展 3. 插值查找算法3.1. 前言3.2. 相关概念3.3. 实例应用 4. 斐波那契(黄金分割法)查找算法4.1. 斐波那契(黄金分割法)原理4.2. 实例应用 查找算法介绍 在 java 中&#xff0c;我们…

Linux快速搭建本地yum更新audit

场景&#xff1a;内网一台服务器上线&#xff0c;需要更新audit版本&#xff0c;因无法与其他服务器通信&#xff0c;需临时配置本地仓库。 1、上传新版本操作系统iso到服务器 2、创建yum仓库文件存储目录 mkdir /opt/myrepo 3、挂载磁盘到/mnt mount /opt/Kylin-Server-V…

电脑CentOS 7.6与Windows系统对比:使用方式、优缺点概述

在多操作系统环境中&#xff0c;CentOS 7.6和Windows系统各自独占鳌头&#xff0c;它们在功能、稳定性、兼容性以及安全性等方面都有着各自的优点。这篇文章将对比分析这两个操作系统&#xff0c;以便用户能更好地了解它们的特点和使用方式。 一、使用方式 CentOS 7.6 CentO…

探索Web前端技术的变革与未来发展

Web前端技术作为构建现代互联网应用的重要一环&#xff0c;自诞生以来已经经历了多轮的发展和变革。本文将回顾过去的进展&#xff0c;介绍当前的前端技术栈&#xff0c;并展望未来前端领域的发展趋势&#xff0c;包括新兴技术和重要概念。 引言 在信息时代的快速发展的背景下&…

【剑指offer|图解|位运算】训练计划VI+撞色搭配

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、剑指offer每日一练 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️训练计划VI&#xff08;题目难度&#xff1a;中等&#xff09;1.1 题目1.2 示例1.3 …

读书笔记-《数据结构与算法》-摘要3[选择排序]

选择排序 核心&#xff1a;不断地选择剩余元素中的最小者。 找到数组中最小元素并将其和数组第一个元素交换位置。在剩下的元素中找到最小元素并将其与数组第二个元素交换&#xff0c;直至整个数组排序。 性质&#xff1a; 比较次数(N-1)(N-2)(N-3)…21~N^2/2交换次数N运行…

基于ssm vue的风景文化管理平台源码和论文

摘 要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;基于vue的木里风景文化管理平台也不例外&#xff0c;但目前国内的市场仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对…

SpringBoot集成mail发送邮件

前言 发送邮件功能&#xff0c;借鉴 刚果商城&#xff0c;根据文档及项目代码实现。整理总结便有了此文&#xff0c;文章有不对的点&#xff0c;请联系博主指出&#xff0c;请多多点赞收藏&#xff0c;您的支持是我最大的动力~ 发送邮件功能主要借助 mail、freemarker以及rocke…

CoreDNS实战(七)-日志处理

本文主要用于介绍CoreDNS用来记录日志的几种方式以及在生产环境中遇到的一些问题和解决方案。 1 log插件 coredns的日志输出并不如nginx那么完善&#xff08;并不能在配置文件中指定输出的文件目录&#xff0c;但是可以指定日志的格式&#xff09;&#xff0c;默认情况下不论…

【Midjourney实战】| 新年礼盒元素设计

文章目录 1 初步提示词2 润色提示词3 提示词发散联想 这期实践任务&#xff0c;我们想去做一个新年礼盒的效果&#xff0c;最后我们想把不同元素拼在一起&#xff0c;方便后期进行新年的相关设计 1 初步提示词 提示词初步我们乍一想&#xff0c;肯定要包括主体元素礼盒 新年礼…

Verilog基础:$time、$stime和$realtime系统函数的使用

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html $time、 $stime和$realtime这三个系统函数提供了返回当前仿真时间方法。注意&#xff0c;这里的仿真时间的最小分辨能力是由仿真时间精度决定的&#xff0c;简单来说&#xff0c;可以理解为…

gpt阅读论文利器

1. txyz.ai 读论文 严伯钧 3. consensus 两亿科学论文的资源库. 用英文. 中国经济发展, 美国加州没有,减肥没有. 2. chrome插件 gpt sidebar 3. gpt academic 论文润色和学术翻译 ,一键输出公式. 英语口语8000句. 托福备考计划表. 百词斩托福. 薄荷外刊. 分区笔记精读法.…