版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第十二章 多线程,多线程基本概念 创建线程的方式 线程的生命周期及控制 线程的优先级及调度 多线程的互斥与同步 守护线程 (Daemon) 线程组 (ThreadGroup),线程的概念,进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。对一个应用程序可以同时启动多个进程。例如每次执行JDK的java.exe程序,就启动了一个独立的Java虚拟机进程,该进程的任务是解析并执行Java程序代码。 线程是指进程中的一个执行流程,有时也称为执行情景。一个进程可以由多个线程组成,即在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务。当进程内的多个线程同时运行,这种运行方式称为并发
2、运行。,并发现象在现实生活中大量存在 人体(消化、运动) 计算机(同时运行多中程序) 多线程在一个程序中实现并发 编程语言一般提供了串行程序设计的方法 计算机的并发能力由操作系统提供 Java在语言级提供多线程并发的概念,1、多线程基本概念,1、多线程基本概念,以前所编写的程序,每个程序都有一个入口、一个出口以及一个顺序执行的序列,在程序执行过程中的任何指定时刻,都只有一个单独的执行点。 事实上,在单个程序内部是可以在同一时刻进行多种运算的,这就是所谓的多线程(这与多任务的概念有相似之处)。 一个单独的线程和顺序程序相似,也有一个入口、一个出口以及一个顺序执行的序列,从概念上说,一个线程是一个
3、程序内部的一个顺序控制流。 线程并不是程序,它自己本身并不能运行,必须在程序中运行。在一个程序中可以实现多个线程,这些线程同时运行,完成不同的功能。,1、多线程基本概念,从逻辑的观点来看,多线程意味着一个程序的多行语句同时执行,但是多线程并不等于多次启动一个程序,操作系统也不会把每个线程当作独立的进程来对待: 两者的粒度不同,是两个不同层次上的概念。进程是由操作系统来管理的,而线程则是在一个程序(进程)内。 不同进程的代码、内部数据和状态都是完全独立的,而一个程序内的多线程是共享同一块内存空间和同一组系统资源,有可能互相影响。 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所
4、以线程的切换比进程切换的负担要小。,1、多线程基本概念,进程:程序的一次执行。 程序代码 程序数据 程序资源 线程:进程中程序代码的一个执行序列。 程序调用堆栈 线程局部变量 可共享访问进程中的数据和资源 操作系统按线程来调度程序的执行,1、多线程基本概念,1、多线程基本概念,1、多线程基本概念,多线程的优势: 多线程编程简单,效率高(能直接共享数据和资源,多进程不能) 适合于开发服务程序(如Web服务,聊天服务等) 适合于开发有多种交互接口的程序(如聊天程序的客户端,网络下载工具) 适合于有人机交互又有计算量的程序(如字处理程序Word,Excel) 减轻编写交互频繁、涉及面多的程序的困难(
5、如监听网络端口) 程序的吞吐量会得到改善(同时监听多种设备,如网络端口、串口、并口以及其他外设) 有多个处理器的系统,可以并发运行不同的线程(否则,任何时刻只有一个线程在运行),1、多线程基本概念,虽然各种操作系统(Unix/Linux、Windows系列等)都支持多线程,但若要用C、C+或其他语言编写多线程程序是十分困难的,因为它们对数据同步的支持不充分。 对多线程的综合支持是Java语言的一个重要特色,它提供了Thread类来实现多线程。在Java中,线程可以认为是由三部分组成的: 虚拟CPU,封装在java.lang.Thread类中,它控制着整个线程的运行; 执行的代码,传递给Thre
6、ad类,由Thread类控制顺序执行; 处理的数据,传递给Thread类,是在代码执行过程中所要处理的数据。,2、创建线程的方式,Java的线程是通过Java的软件包java.lang中定义的类Thread来实现的。当生成一个Thread类的对象之后,就产生了一个线程,通过该对象实例,可以启动线程、终止线程、或者暂时挂起线程等。,public class MyThread extends Thread public void run() for(int a=0;a50;a+) tryThread.sleep(100);catch(Exception e) System.out.println(
7、Thread.currentThread().getName()+ +a); public static void main(String args) MyThread t1=new MyThread(); MyThread t2=new MyThread(); t1.start(); t2.start(); ,2、创建线程的方式,Thread类本身只是线程的虚拟CPU; 线程所执行的代码(或者说线程所要完成的功能)是通过方法run( )(包含在一个特定的对象中)来完成的,方法run( )称为线程体。实现线程体的特定对象是在初始化线程时传递给线程的。 在一个线程被建立并初始化以后,Java的运
8、行时系统就自动调用run( )方法,正是通过run( )方法才使得建立线程的目的得以实现。 通常,run( )方法是一个循环,例如一个播放动画的线程要循环显示一系列图片。有时,run( )方法会执行一个时间较长的操作,例如下载并播放一个JPEG格式的电影。,先来看看线程对象的初始化,类Thread的构造方法如下: public Thread( ThreadGroup group, Runnable target, String name) group指明了线程所属的线程组; target是线程体run()方法所在的对象; name是线程的名称。 target必须实现接口Runnable。在接口
9、Runnable中只定义了一个方法void run()作为线程体。任何实现接口Runnable的对象都可以作为一个线程的目标对象。 类Thread本身也实现了接口Runnable(空实现),因此,上述构造方法中各参数都可以为null。,2、创建线程的方式,从Thread类的构造方法可以看出,用户可以有两种方法构造自己的run( )方法。 方法一: 定义一个线程类,它继承类Thread并重写其中的方法run()。这时在初始化这个类的实例时,目标对象target可以为null,表示这个实例本身具有线程体。由于Java只支持单继承,用这种方法定义的类不能再继承其他类。,2、创建线程的方式,class
10、 SimpleThread extends Thread public SimpleThread(String str) super(str); public void run() for (int i = 0; i 10; i+) System.out.println(i + + getName(); try sleep(int)(Math.random() * 1000); catch (InterruptedException e) System.out.println(DONE! + getName(); ,TwoThreadsTest.java,2、创建线程的方式,方法二: 提供一个
11、实现接口Runnable的类作为线程的目标对象。在初始化一个Thread类或子类生成线程实例时,把目标对象传递给这个线程实例,由该目标对象提供线程体run()方法。这时,实现接口Runnable的类还可以再继承其他类,即实现接口Runnable的类可以不单纯是提供线程体。,2、创建线程的方式,public class Clock extends java.applet.Applet implements Runnable Thread clockThread; public void start() if (clockThread = null) clockThread = new Threa
12、d(this, Clock); clockThread.start(); public void run() while (clockThread != null) repaint(); try clockThread.sleep(1000); catch (InterruptedException e) public void paint(Graphics g) Date now = new Date(); g.drawString(now.getHours() + : + now.getMinutes() + : + now.getSeconds(), 5, 10); public voi
13、d stop() clockThread.stop(); clockThread = null; ,Clock.java,run ()方法是运行线程的主体,启动线程时,由java直接调用public void run()。 使用Runnable接口可以将线程的虚拟CPU、代码和数据分开,形成一个比较清晰的模型。 使用Runnable接口使得包含线程体的类还可以继承其他类。 直接继承Thread类以后不能再继承其他类,但编写简单,并可直接操纵线程;使用Runnable接口时,若要在run()方法中操纵线程,必须使用Thread.currentThread()方法。 在具体应用中,采用哪种方法来构
14、造线程体要根据具体情况而定。通常,当一个线程体所在的类已经继承了另一个类时,就应该用实现Runnable接口的方法。,2、创建线程的方式,2、创建线程的方式,Thread类的方法 void run() 线程所执行的代码 void start() throws IllegalThreadStateException 使程序开始执行,多次调用会产生例外 void sleep(long milis) 让线程睡眠一段时间,此期间线程不消耗CPU资源 void interrupt() 中断线程 static boolean interrupted() 判断当前线程是否被中断(会清除中断状态标记) boo
15、lean isInterrupted() 判断指定线程是否被中断,线程状态控制方法,2、创建线程的方式,Thread类的方法 boolean isAlive() 判断线程是否处于活动状态(即已调用start,但run还未返回) static Thread currentThread() 返回当前线程对象的引用 void setName(String threadName) 改变线程的名字 String getName() 获得线程的名字 String toString() 例子:ThreadThread-0,1,test void join(longmillis, intnanos) 等待线程
16、结束,2、创建线程的方式,Thread类的方法 void destroy() 销毁线程 void stop() 终止线程的执行 void suspend() / void resume() 挂起线程 / static void yield() 暂停当前线程,让其他线程执行 void setDaemon(boolean on) / void setPriority(int p) 获得线程的名字 notify() / notifyAll() / wait() 从Object继承而来,3、 线程的生命周期及控制,线程是程序内部的一个顺序控制流,它具有一个特定的生命周期。在一个线程的生命周期中,它总处
17、于某一种状态中。线程的状态表示了线程正在进行的活动以及在这段时间内线程能完成的任务。,3、线程的生命周期及控制,born,ready,running,waiting,sleeping,dead,blocked,start(),dispatch,quantum expiration yield(),wait,sleep(),run complete,issue I/O request,wait interval expires notify() notifyAll() interrupt(),I/O complete,sleep interval expires interrupt(),3、线程
18、的生命周期及控制,3、线程的生命周期及控制,创建状态(new Thread) 当创建了一个新的线程时( myThread thd = new myThread(); ),它就处于创建状态,此时它仅仅是一个空的线程对象,系统不为它分配资源。处于这种状态时只能启动或终止该线程,调用除这两种以外的其它线程状态相关的方法都会失败并且会引起非法状态例外IllegalThreadStateException(对于其它状态,若所调用的方法与状态不符,都会引起非法状态例外)。,可运行状态(Runnable) 当线程处于创建状态时,可以调用start()方法( thd.start(); )来启动它,产生运行这个
19、线程所需的系统资源,安排其运行,并调用线程体run()方法,这样就使得该线程处于可运行( Runnable )状态。 需要注意的是这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行(Ready)。由于很多计算机都是单处理器的,所以要在同一时刻运行所有的处于可运行状态的线程是不可能的,Java运行系统必须实现调度来保证这些线程共享处理器。但是在大多数情况下,可运行状态也就是运行中。当一个线程正在运行时,它是可运行的,并且也是当前正运行的线程。,3、线程的生命周期及控制,3、线程的生命周期及控制,不可运行状态(Not Runnable) 线程处于可运行状态时,当下面四种情
20、况发生,线程就进入不可运行状态: 调用了sleep()方法; 调用了suspend()方法; 为等候一个条件变量,线程调用wait()方法; 输入输出流中发生线程阻塞。,Thread myThread = new MyThreadClass(); myThread.start(); try myThread.sleep(10000); catch (InterruptedException e) ,不可运行状态(Not Runnable) 对于这四种使得线程处于不可运行状态的情况,都有特定的方法使线程返回可运行状态: 如果线程处于睡眠状态中,sleep()方法中的参数为休息时间,当这个时间过去
21、后,线程即为可运行的; 如果一个线程被挂起,须调用resume()方法来返回; 如果线程在等待条件变量,那么要停止等待的话,需要该条件变量所在的对象调用notify()或notifyAll()方法; 如果在I/O流中发生线程阻塞,则特定的I/O指令将结束这种不可运行状态。 需要注意的是每种方法都仅仅对相应的情况才有作用,例如当一个线程睡眠并且睡眠时间还没有结束时,调用resume()方法是无效的,并且还会引起非法状态例外。,3、 线程的生命周期及控制,死亡状态(Dead) 线程的终止一般可通过两种方法实现:自然撤消或是被停止。自然撤消是指从线程的run()方法正常退出;而调用线程的实例方法st
22、op()则可以强制停止当前线程。,3、线程的生命周期及控制,/线程自然撤销 public void run() int i = 0; while (i 100) i+; System.out.println(i = + i); ,/线程被强行停止 myThread thd = new MyThread(); thd.start( ); try thd.sleep(10000); catch (InterruptedException e) thd.stop( );,3、线程的生命周期及控制,可以通过在其他线程中调用stop()方法来终止线程,也可以由线程自己调用stop()方法,自我终止。 如
23、果希望线程正常终止,可采用标记来使线程中的run()方法退出。,/线程自我终止 public void run() while ( true ) /完成线程的特定功能 if( time_to_die ) Thread.currentThread().stop(); ,public class Xyz implements Runnable private boolean timeToQuit = false; public void run() while( !timeToQuit ) public void stopRunning() timeToQuit = true; ,3、线程的生命周
24、期及控制,public class ControlThread private Xyz r = new Xyz(); private Thread t = new Thread(r); public void startThread() t.start(); publi void stopThread() r.stopRunning(); ,3、线程的生命周期及控制,isAlive()方法:可以用来判断线程目前是否正在执行中。如果线程已被启动并且未被终止,那么isAlive( )返回true,但该线程是可运行或是不可运行的,是不能作进一步的分辨。如果返回false,则该线程是新创建或是已被终止
25、的(同样不能作进一步的分辨)。 join()方法:等待线程执行完毕。 yield()方法:将执行的权力交给其它优先级相同的线程,当前执行线程到可运行线程队列(ready)的最后等待,若队列空,该方法无效。,myThread thd = new MyThread(); thd.start(); thd.join(); /等待线程thd执行完后再继续往下执行 ,join(int time):最多等待time所指定的时间。,sleep()方法可以暂停线程的执行,让其它线程得到机会。但sleep()要丢出例外InterruptedException ,必须抓住。 try sleep(100) catc
26、h(InterruptedException e) /本例外可不作处理 suspend()方法和resume()可以用来暂停线程或恢复线程。可以由线程自身在线程体中调用suspend()方法暂停自己,也可以在其他线程中通过线程实例调用suspend()方法暂停线程的执行。但是要恢复由suspend()方法暂停的线程,只能在其他线程中通过线程实例调用resume()方法。,3、线程的生命周期及控制,Java提供一个线程调度器来监控程序中启动后进入可运行状态的所有线程。线程调度器按照线程的优先级决定调度哪些线程来执行,具有高优先级的线程会在较低优先级的线程之前得到执行。同时线程的调度是抢先式的,即
27、如果当前线程在执行过程中,一个具有更高优先级的线程进入可执行状态,则该告优先级的线程会被立即调度执行。 多个线程运行时,若线程的优先级相同,由操作系统按时间片轮转方式或独占方式来分配线程的执行时间。,4、线程的优先级及调度,在Java中线程的优先级是用数字来表示的,分为三个级别: 低优先级:Thread.MIN_PRIORITY,数值为1 (24) 缺省优先级: Thread. NORM_PRIORITY,数值为5 高优先级:Thread.MAX_PRIORITY,数值为10 (69) 具有相同优先级的多个线程,若它们都为高优先级Thread.MAX_PRIORITY,则每个线程都是独占式的,
28、也就是说这些线程将被顺序执行;若该优先级不为高优先级,则这些线程将同时执行,也就是说这些线程的执行是无序的。 线程被创建后,其缺省的优先级是缺省优先级Thread. NORM_PRIORITY。可以用方法 int getPriority()来获得线程的优先级,同时也可以用方法 void setPriority( int p ) 在线程被创建后改变线程的优先级。,4、线程的优先级及调度,4、线程的优先级及调度,线程离开执行状态, 如果: 线程结束 线程处于I/O等待 线程调用sleep 线程调用wait 线程调用join 线程调用yield 有更高优先级的线程 时间片用完,ThreadPrior
29、ity.java,1. 用管道流实现线程间的通信,2. 通过一个中间类在线程间传递信息,5、多线程的互斥与同步 线程间的通信,管道流可以连接两个线程间的通信。下面的例子里有两个线程在运行,一个写线程往管道流中输出信息,一个读线程从管道流中读入信息。,5、多线程的互斥与同步 线程间的通信,class myWriter extends Thread private PipedOutputStream outStream; /将数据输出 private String messages = Monday, Tuesday , Wednsday, Thursday,Friday, Saturday, S
30、unday ; public myWriter(PipedOutputStream o) outStream = o; public void run() PrintStream p = new PrintStream( outStream ); for( int i = 0; i messages.length; i+) p.println( messagesi ); p.flush(); System.out.println(Write: + messagesi ); p.close(); p = null; ,class myReader extends Thread private P
31、ipedInputStream inStream; /从中读数据 public myReader(PipedInputStream i) inStream = i; public void run() String line; DataInputStream d; boolean reading = true; d = new DataInputStream( inStream ); while( reading catch( InterruptedException e ) ,public class Pipethread public static void main(String arg
32、s) Pipethread thisPipe = new Pipethread(); thisPcess(); public void process() PipedInputStream inStream; PipedOutputStream outStream; try outStream = new PipedOutputStream(); inStream = new PipedInputStream(outStream); new myWriter( outStream ).start(); new myReader( inStream ).start(); catch
33、( IOException e ) ,Pipethread.java,5、多线程的互斥与同步 线程间的资源互斥共享,通常,一些同时运行的线程需要共享数据。在这种时候,每个线程就必须要考虑其他与他一起共享数据的线程的状态与行为,否则的话就不能保证共享数据的一致性,从而也就不能保证程序的正确性。,class Stack int index = 0; char data = new char6; public void push(char c) dataindex = c; index+; public char pop() index-; return dataindex; ,当有两个线程A和B同
34、时使用了Stack类的一个实例时,在某一时刻,A要往堆栈里push数据,而B则要从堆栈中pop数据: (1)操作之前,堆栈中有两个字符: data = | a | c | | | | | index = 2 (2)A执行push中的第一条语句dataindex = r: data = | a | c | r | | | | index = 2 (3)A还没有执行index+语句,A被B中断,B执行pop()方法,返回c: data = | a | c | r | | | | index = 1 (4)A继续执行index+语句: data = | a | c | r | | | | index
35、 = 2 最后的结果是r并没有添加到堆栈中去。,5、多线程的互斥与同步 线程间的资源互斥共享,class bank static double balance; public boolean 取钱(double amount) if(balance = amount ) balance -= amount; return true; else return false; ,5、多线程的互斥与同步 线程间的资源互斥共享,产生这种问题的原因是对共享资源访问的不完整。为了解决这种问题,需要寻找一种机制来保证对共享数据操作的完整性,这种完整性称为共享数据操作的同步,共享数据叫做条件变量。 在Java语
36、言中,引入了“对象互斥锁”的概念(又称为监视器、管程)来实现不同线程对共享数据操作的同步。 “对象互斥锁”阻止多个线程同时访问同一个条件变量。 在Java语言中,有两种方法可以实现“对象互斥锁”: 用关键字volatile来声明一个共享数据(变量); 用关键字synchronized来声明一个操作共享数据的方法或一段代码。,5、多线程的互斥与同步 线程间的资源互斥共享,class stack int index = 0; char data = new char6; public synchronized void push(char c) dataindex = c; index+; pub
37、lic synchronized char pop() index-; return dataindex; ,class bank static volatile double balance; public syn boolean 取钱(double amt) if(balance = amt ) balance -= amt; return true; else return false; ,class stack public void push(char c) synchronized(this) dataindex = c; index+; public char pop() ,5、
38、多线程的互斥与同步 线程间的资源互斥共享,用synchronized来标识的代码段或方法即为“对象互斥锁”锁住的部分。如果一个程序内有两个或以上的方法使用synchronized标志,则它们在同一个“对象互斥锁”管理之下。,一般情况下,都使用synchronized关键字在方法的层次上实现对共享资源操作的同步,很少使用volatile关键字声明共享变量。,5、多线程的互斥与同步 线程间的资源互斥共享,除了要处理多线程间共享数据操作的同步问题之外,在进行多线程程序设计时,还会遇到另一类问题,这就是如何控制相互交互的线程之间的运行进度,即多线程的同步。 典型的模型:生产者消费者问题,5、多线程的互
39、斥与同步 线程间的同步,若共享对象中只能存放一个数据,可能出现以下问题: 生产者比消费者快时,消费者会漏掉一些数据没有取到; 消费者比生产者快时,消费者取相同的数据。,为了解决所出现的问题,在Java语言中可以用wait () 和notify()/notifyAll()方法(在java.lang.Object类中定义)来协调线程间的运行进度(读取)关系。 wait()方法的作用是让当前线程释放其所持有的“对象互斥锁”,进入wait队列(等待队列);而notify()/notifyAll()方法的作用是唤醒一个或所有正在等待队列中等待的线程,并将它(们)移入等待同一个“对象互斥锁”的队列。 需要
40、指出的是: notify()/notifyAll()方法和wait ()方法都只能在被声明为synchronized的方法或代码段中调用。,5、多线程的互斥与同步 线程间的同步,public class ProducerConsumerTest public static void main(String args) CubbyHole c = new CubbyHole(); /共享资源 Producer p1 = new Producer(c, 1); /资源生产者 Consumer c1 = new Consumer(c, 1); /资源消耗者 p1.setPriority(Thread
41、.MAX_PRIORITY); c1.setPriority(Thread.MAX_PRIORITY); p1.start(); c1.start(); ,class Producer extends Thread private CubbyHole cubbyhole; private int number; public Producer(CubbyHole c, int number) cubbyhole = c; this.number = number; public void run() for (int i = 0; i 10; i+) cubbyhole.put(i); Sys
42、tem.out.println(Producer # + this.number + put: + i); try sleep(int)(Math.random() * 100); catch (InterruptedException e) ,class Consumer extends Thread private CubbyHole cubbyhole; private int number; public Consumer(CubbyHole c, int number) cubbyhole = c; this.number = number; public void run() in
43、t value = 0; for (int i = 0; i 10; i+) value = cubbyhole.get(); System.out.println(Consumer # + this.number + got: + value); ,class CubbyHole private int seq; private boolean available = false; /信号量 public synchronized int get() while (available = false) try wait(); / waits for notify() call from Pr
44、oducer catch (InterruptedException e) available = false; notify(); return seq; ,public synchronized void put(int value) while (available = true) try wait(); / waits for notify() call from consumer catch (InterruptedException e) seq = value; available = true; notify(); ,get()方法在读信息之前先等待,直到信息可读,读完后通知要写的线程。,
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年结核病治疗期间的营养支持讲座
- 2026山西吕梁离石区零工市场公益性岗位人员招聘5人备考题库及答案详解(名师系列)
- 2026贵州航天医院助理全科医生(西医)培训招录25人备考题库及答案详解(必刷)
- 2026北京市疾病预防控制中心面向社会人员招聘26人备考题库(第一批)及参考答案详解一套
- 2026内蒙古锡林郭勒盟东乌珠穆沁旗事业单位引进急需紧缺人才3人备考题库附答案详解
- 2026年生物制药质量文化建设实施方案
- 2026浙江杭州滨江水务有限公司招聘12人笔试备考题库及答案解析
- 2026湖北恩施州宣恩县展宏粮食储备有限公司招聘1人考试模拟试题及答案解析
- 2026生态环境研究中心区域与城市生态安全全国重点实验室城市代谢与产业生态研究组研究助理岗位招聘(北京)笔试模拟试题及答案解析
- 2026年甘肃省酒泉市市直事业单位选调33人(第一批)笔试备考试题及答案解析
- 水泵吊装施工方案
- IT-IT开发-通用-L1题目分享
- 2022年浙江衢州市大花园集团招聘31人上岸笔试历年难、易错点考题附带参考答案与详解
- 火龙罐技术课件
- 美的中央空调系统投标书正文
- 劳动纠纷应急预案
- 培训中心手绘技能培训马克笔单体表现
- cobb肉鸡饲养管理手册
- 妙用人工智能工具绘制“山水诗城”画卷-初识AI绘画 了解手写数字识别-体验人工智能 第四单元第5课时
- YC/T 205-2017烟草及烟草制品仓库设计规范
- GB/T 9065.3-2020液压传动连接软管接头第3部分:法兰式
评论
0/150
提交评论