* 进程:
* 正在运行的程序,是系统进行资源分配和调用的独立单位。
* 每一个进程都有它自己的内存空间和系统资源。
* 线程:
* 是进程中的单个顺序控制流,是一条执行路径
* 一个进程如果只有一条执行路径,则称为单线程程序。
* 一个进程如果有多条执行路径,则称为多线程程序。
*
* 大家注意两个词汇的区别:并行和并发。
* 前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
* 后者是物理上同时发生,指在某一个时间点同时运行多个程序。
*
* Java程序的运行原理:
* 由java命令启动JVM,JVM启动就相当于启动了一个进程。
* 接着有该进程创建了一个主线程去调用main方法。
*
* 思考题:
* jvm虚拟机的启动是单线程的还是多线程的?
* 多线程的。
* 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
* 现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
* 方式1:继承Thread类。
* 步骤
* A:自定义类MyThread继承Thread类。
* B:MyThread类里面重写run()?
* 为什么是run()方法呢?
* C:创建对象
* D:启动线程
1 /* 2 * 该类要重写run()方法,为什么呢? 3 * 不是类中的所有代码都需要被线程执行的。 4 * 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。 5 */ 6 public class MyThread extends Thread { 7 8 @Override 9 public void run() { 10 // 自己写代码 11 // System.out.println("好好学习,天天向上"); 12 // 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进 13 for (int x = 0; x < 200; x++) { 14 System.out.println(x); 15 } 16 } 17 }
// 调用run()方法为什么是单线程的呢?
// 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
// 要想看到多线程的效果,就必须说说另一个方法:start()
// 面试题:run()和start()的区别?
// run():仅仅是封装被线程执行的代码,直接调用是普通方法
// start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
// MyThread my = new MyThread();
// my.start();
// // IllegalThreadStateException:非法的线程状态异常
// // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
// my.start();
1 // 创建两个线程对象 2 MyThread my1 = new MyThread(); 3 MyThread my2 = new MyThread(); 4 5 my1.start(); 6 my2.start();
* 如何获取线程对象的名称呢?
* public final String getName():获取线程的名称。
* 如何设置线程对象的名称呢?
* public final void setName(String name):设置线程的名称
*
* 针对不是Thread类的子类中如何获取线程对象名称呢?
* public static Thread currentThread():返回当前正在执行的线程对象
* Thread.currentThread().getName()
方法一:
1 // 创建线程对象 2 //无参构造+setXxx() 3 MyThread my1 = new MyThread(); 4 MyThread my2 = new MyThread(); 5 // //调用方法设置名称 6 my1.setName("林青霞"); 7 my2.setName("刘意"); 8 my1.start(); 9 my2.start();
方法二:
1 //带参构造方法给线程起名字 2 MyThread my1 = new MyThread("林青霞"); 3 MyThread my2 = new MyThread("刘意"); 4 my1.start(); 5 my2.start(); 6 7 //我要获取main方法所在的线程对象的名称,该怎么办呢? 8 //遇到这种情况,Thread类提供了一个很好玩的方法: 9 //public static Thread currentThread():返回当前正在执行的线程对象 10 System.out.println(Thread.currentThread().getName());
1 public class MyThread extends Thread { 2 3 public MyThread() { 4 } 5 6 public MyThread(String name){ 7 super(name); 8 } 9 10 @Override 11 public void run() { 12 for (int x = 0; x < 100; x++) { 13 System.out.println(getName() + ":" + x); 14 } 15 } 16 }
* 我们的线程没有设置优先级,肯定有默认优先级。
* 那么,默认优先级是多少呢?
* 如何获取线程对象的优先级?
* public final int getPriority():返回线程对象的优先级
* 如何设置线程对象的优先级呢?
* public final void setPriority(int newPriority):更改线程的优先级。
*
* 注意:
* 线程默认优先级是5。
* 线程优先级的范围是:1-10。
* 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
*
* IllegalArgumentException:非法参数异常。
* 抛出的异常表明向方法传递了一个不合法或不正确的参数。
1 ThreadPriority tp1 = new ThreadPriority(); 2 ThreadPriority tp2 = new ThreadPriority(); 3 ThreadPriority tp3 = new ThreadPriority(); 4 5 tp1.setName("东方不败"); 6 tp2.setName("岳不群"); 7 tp3.setName("林平之"); 8 9 // 获取默认优先级 10 // System.out.println(tp1.getPriority()); 11 // System.out.println(tp2.getPriority()); 12 // System.out.println(tp3.getPriority()); 13 14 // 设置线程优先级 15 // tp1.setPriority(100000); 16 17 //设置正确的线程优先级 18 tp1.setPriority(10); 19 tp2.setPriority(1); 20 21 tp1.start(); 22 tp2.start(); 23 tp3.start();
* 线程休眠
* public static void sleep(long millis)
* public final void join():等待该线程终止。
等线程tj1执行完毕才执行其他线程
1 ThreadJoin tj1 = new ThreadJoin(); 2 ThreadJoin tj2 = new ThreadJoin(); 3 ThreadJoin tj3 = new ThreadJoin(); 4 5 tj1.setName("李渊"); 6 tj2.setName("李世民"); 7 tj3.setName("李元霸"); 8 9 tj1.start(); 10 try { 11 tj1.join(); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 16 tj2.start(); 17 tj3.start();
* public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
* 让多个线程的执行更和谐,但是不能靠它保证一人一次。
* public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
* 当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
1 public static void main(String[] args) { 2 ThreadDaemon td1 = new ThreadDaemon(); 3 ThreadDaemon td2 = new ThreadDaemon(); 4 5 td1.setName("关羽"); 6 td2.setName("张飞"); 7 8 // 设置收获线程 9 td1.setDaemon(true); 10 td2.setDaemon(true); 11 12 td1.start(); 13 td2.start(); 14 15 Thread.currentThread().setName("刘备"); 16 for (int x = 0; x < 5; x++) { 17 System.out.println(Thread.currentThread().getName() + ":" + x); 18 } 19 }
* public final void stop():让线程停止,过时了,但是还可以使用。
* public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
线程的生命周期:
* 方式2:实现Runnable接口
* 步骤:
* A:自定义类MyRunnable实现Runnable接口
* B:重写run()方法
* C:创建MyRunnable类的对象
* D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
1 public class MyRunnable implements Runnable { 2 3 @Override 4 public void run() { 5 for (int x = 0; x < 100; x++) { 6 // 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用 7 System.out.println(Thread.currentThread().getName() + ":" + x); 8 } 9 } 10 11 }
1 public static void main(String[] args) { 2 // 创建MyRunnable类的对象 3 MyRunnable my = new MyRunnable(); 4 5 // 创建Thread类的对象,并把C步骤的对象作为构造参数传递 6 // Thread(Runnable target) 7 // Thread t1 = new Thread(my); 8 // Thread t2 = new Thread(my); 9 // t1.setName("林青霞"); 10 // t2.setName("刘意"); 11 12 // Thread(Runnable target, String name) 13 Thread t1 = new Thread(my, "林青霞"); 14 Thread t2 = new Thread(my, "刘意"); 15 16 t1.start(); 17 t2.start(); 18 }
多线程两种方式的图解比较和区别:
* 实现Runnable接口的方式实现
*
* 通过加入延迟后,就产生了连个问题:
* A:相同的票卖了多次
* CPU的一次操作必须是原子性的
* B:出现了负数票
* 随机性和延迟导致的
* 思想:
* 把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
* 问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。
*
* 同步代码块:
* synchronized(对象){
* 需要同步的代码;
* }
*
* A:对象是什么呢?
* 我们可以随便创建一个对象试试。
* B:需要同步的代码是哪些呢?
* 把多条语句操作共享数据的代码的部分给包起来
*
* 注意:
* 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
* 多个线程必须是同一把锁。
1 // 定义100张票 2 private int tickets = 100; 3 4 // 定义同一把锁 5 private Object obj = new Object(); 6 7 @Override 8 public void run() { 9 while (true) { 10 // t1,t2,t3都能走到这里 11 // 假设t1抢到CPU的执行权,t1就要进来 12 // 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。 13 // 门(开,关) 14 synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关) 15 if (tickets > 0) { 16 try { 17 Thread.sleep(100); // t1就睡眠了 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println(Thread.currentThread().getName() 22 + "正在出售第" + (tickets--) + "张票 "); 23 //窗口1正在出售第100张票 24 } 25 } //t1就出来可,然后就开门。(开) 26 } 27 }
* 同步的特点:
* 前提:
* 多个线程
* 解决问题的时候要注意:
* 多个线程使用的是同一个锁对象
* 同步的好处
* 同步的出现解决了多线程的安全问题。
* 同步的弊端
* 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
* A:同步代码块的锁对象是谁呢?
* 任意对象。
*
* B:同步方法的格式及锁对象问题?
* 把同步关键字加在方法上。
*
* 同步方法是谁呢?
* this
*
* C:静态方法及锁对象问题?
* 静态方法的锁对象是谁呢?
* 类的字节码文件对象。
1 // 线程安全的类 2 StringBuffer sb = new StringBuffer(); 3 Vector<String> v = new Vector<String>(); 4 Hashtable<String, String> h = new Hashtable<String, String>(); 5 6 // Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你 7 // 那么到底用谁呢? 8 // public static <T> List<T> synchronizedList(List<T> list) 9 List<String> list1 = new ArrayList<String>();// 线程不安全 10 List<String> list2 = Collections 11 .synchronizedList(new ArrayList<String>()); // 线程安全
* 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
* 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
*
* Lock:
* void lock(): 获取锁。
* void unlock():释放锁。
* ReentrantLock是Lock的实现类.
1 public class SellTicket implements Runnable { 2 3 // 定义票 4 private int tickets = 100; 5 6 // 定义锁对象 7 private Lock lock = new ReentrantLock(); 8 9 @Override 10 public void run() { 11 while (true) { 12 try { 13 // 加锁 14 lock.lock(); 15 if (tickets > 0) { 16 try { 17 Thread.sleep(100); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println(Thread.currentThread().getName() 22 + "正在出售第" + (tickets--) + "张票"); 23 } 24 } finally { 25 // 释放锁 26 lock.unlock(); 27 } 28 } 29 } 30 31 }
死锁:
1 public class DieLock extends Thread { 2 3 private boolean flag; 4 5 public DieLock(boolean flag) { 6 this.flag = flag; 7 } 8 9 @Override 10 public void run() { 11 if (flag) { 12 synchronized (MyLock.objA) { 13 System.out.println("if objA"); 14 synchronized (MyLock.objB) { 15 System.out.println("if objB"); 16 } 17 } 18 } else { 19 synchronized (MyLock.objB) { 20 System.out.println("else objB"); 21 synchronized (MyLock.objA) { 22 System.out.println("else objA"); 23 } 24 } 25 } 26 } 27 }
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。
*
* 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
* A:同一个数据出现多次
* B:姓名和年龄不匹配
* 原因:
* A:同一个数据出现多次
* CPU的一点点时间片的执行权,就足够你执行很多次。
* B:姓名和年龄不匹配
* 线程运行的随机性
* 线程安全问题:
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
* 解决方案:
* 加锁。
* 注意:
* A:不同种类的线程都要加锁。
* B:不同种类的线程加的锁必须是同一把。
*
* 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
* 如何实现呢?
* 通过Java提供的等待唤醒机制解决。
*
* 等待唤醒:
* Object类中提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
* 所以,这些方法必须定义在Object类中。
1 public class Student { 2 private String name; 3 private int age; 4 private boolean flag; // 默认情况是没有数据,如果是true,说明有数据 5 6 public synchronized void set(String name, int age) { 7 // 如果有数据,就等待 8 if (this.flag) { 9 try { 10 this.wait(); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 16 // 设置数据 17 this.name = name; 18 this.age = age; 19 20 // 修改标记 21 this.flag = true; 22 this.notify(); 23 } 24 25 public synchronized void get() { 26 // 如果没有数据,就等待 27 if (!this.flag) { 28 try { 29 this.wait(); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 } 34 35 // 获取数据 36 System.out.println(this.name + "---" + this.age); 37 38 // 修改标记 39 this.flag = false; 40 this.notify(); 41 } 42 }
1 public class GetThread implements Runnable { 2 private Student s; 3 4 public GetThread(Student s) { 5 this.s = s; 6 } 7 8 @Override 9 public void run() { 10 while (true) { 11 s.get(); 12 } 13 } 14 }
1 public class SetThread implements Runnable { 2 3 private Student s; 4 private int x = 0; 5 6 public SetThread(Student s) { 7 this.s = s; 8 } 9 10 @Override 11 public void run() { 12 while (true) { 13 if (x % 2 == 0) { 14 s.set("林青霞", 27); 15 } else { 16 s.set("刘意", 30); 17 } 18 x++; 19 } 20 } 21 }
1 public class StudentDemo { 2 public static void main(String[] args) { 3 //创建资源 4 Student s = new Student(); 5 6 //设置和获取的类 7 SetThread st = new SetThread(s); 8 GetThread gt = new GetThread(s); 9 10 //线程类 11 Thread t1 = new Thread(st); 12 Thread t2 = new Thread(gt); 13 14 //启动线程 15 t1.start(); 16 t2.start(); 17 } 18 }
线程的状态转换图及常见执行情况:
* 线程组: 把多个线程组合到一起。
* 它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
1 MyRunnable my = new MyRunnable(); 2 Thread t1 = new Thread(my, "林青霞"); 3 Thread t2 = new Thread(my, "刘意"); 4 // 我不知道他们属于那个线程组,我想知道,怎么办 5 // 线程类里面的方法:public final ThreadGroup getThreadGroup() 6 ThreadGroup tg1 = t1.getThreadGroup(); 7 ThreadGroup tg2 = t2.getThreadGroup(); 8 // 线程组里面的方法:public final String getName() 9 String name1 = tg1.getName(); 10 String name2 = tg2.getName(); 11 System.out.println(name1); 12 System.out.println(name2); 13 // 通过结果我们知道了:线程默认情况下属于main线程组 14 // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组 15 System.out.println(Thread.currentThread().getThreadGroup().getName());
1 // ThreadGroup(String name) 2 ThreadGroup tg = new ThreadGroup("这是一个新的组"); 3 4 MyRunnable my = new MyRunnable(); 5 // Thread(ThreadGroup group, Runnable target, String name) 6 Thread t1 = new Thread(tg, my, "林青霞"); 7 Thread t2 = new Thread(tg, my, "刘意"); 8 9 System.out.println(t1.getThreadGroup().getName()); 10 System.out.println(t2.getThreadGroup().getName()); 11 12 //通过组名称设置后台线程,表示该组的线程都是后台线程 13 tg.setDaemon(true);
/*
* 线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
*
* 如何实现线程的代码呢?
* A:创建一个线程池对象,控制要创建几个线程对象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:这种线程池的线程可以执行:
* 可以执行Runnable对象或者Callable对象代表的线程
* 做一个类实现Runnable接口。
* C:调用如下方法即可
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:我就要结束,可以吗?
* 可以。
1 public static void main(String[] args) { 2 // 创建一个线程池对象,控制要创建几个线程对象。 3 // public static ExecutorService newFixedThreadPool(int nThreads) 4 ExecutorService pool = Executors.newFixedThreadPool(2); 5 6 // 可以执行Runnable对象或者Callable对象代表的线程 7 pool.submit(new MyRunnable()); 8 pool.submit(new MyRunnable()); 9 10 //结束线程池 11 pool.shutdown(); 12 }
//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
1 public class MyCallable implements Callable { 2 3 @Override 4 public Object call() throws Exception { 5 for (int x = 0; x < 100; x++) { 6 System.out.println(Thread.currentThread().getName() + ":" + x); 7 } 8 return null; 9 } 10 11 }
* 多线程实现的方式3:
* A:创建一个线程池对象,控制要创建几个线程对象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:这种线程池的线程可以执行:
* 可以执行Runnable对象或者Callable对象代表的线程
* 做一个类实现Runnable接口。
* C:调用如下方法即可
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:我就要结束,可以吗?
* 可以。
1 /* 2 * 线程求和案例 3 */ 4 public class MyCallable implements Callable<Integer> { 5 6 private int number; 7 8 public MyCallable(int number) { 9 this.number = number; 10 } 11 12 @Override 13 public Integer call() throws Exception { 14 int sum = 0; 15 for (int x = 1; x <= number; x++) { 16 sum += x; 17 } 18 return sum; 19 } 20 21 }
1 public class CallableDemo { 2 public static void main(String[] args) throws InterruptedException, ExecutionException { 3 // 创建线程池对象 4 ExecutorService pool = Executors.newFixedThreadPool(2); 5 6 // 可以执行Runnable对象或者Callable对象代表的线程 7 Future<Integer> f1 = pool.submit(new MyCallable(100)); 8 Future<Integer> f2 = pool.submit(new MyCallable(200)); 9 10 // V get() 11 Integer i1 = f1.get(); 12 Integer i2 = f2.get(); 13 14 System.out.println(i1); 15 System.out.println(i2); 16 17 // 结束 18 pool.shutdown(); 19 } 20 }
* 匿名内部类的格式:
1 // 继承Thread类来实现多线程 2 new Thread() { 3 public void run() { 4 for (int x = 0; x < 100; x++) { 5 System.out.println(Thread.currentThread().getName() + ":" 6 + x); 7 } 8 } 9 }.start(); 10 11 // 实现Runnable接口来实现多线程 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 for (int x = 0; x < 100; x++) { 16 System.out.println(Thread.currentThread().getName() + ":" 17 + x); 18 } 19 } 20 }) { 21 }.start();
* 定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。
* 依赖Timer和TimerTask这两个类:
* Timer:定时
* public Timer()
* public void schedule(TimerTask task,long delay)
* public void schedule(TimerTask task,long delay,long period)
* public void cancel()
* TimerTask:任务
1 public class TimerDemo { 2 public static void main(String[] args) { 3 // 创建定时器对象 4 Timer t = new Timer(); 5 // 3秒后执行爆炸任务 6 // t.schedule(new MyTask(), 3000); 7 //结束任务 8 t.schedule(new MyTask(t), 3000); 9 } 10 } 11 12 // 做一个任务 13 class MyTask extends TimerTask { 14 15 private Timer t; 16 17 public MyTask(){} 18 19 public MyTask(Timer t){ 20 this.t = t; 21 } 22 23 @Override 24 public void run() { 25 System.out.println("beng,爆炸了"); 26 t.cancel(); 27 } 28 29 }
需求:在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)
1 class DeleteFolder extends TimerTask { 2 3 @Override 4 public void run() { 5 File srcFolder = new File("demo"); 6 deleteFolder(srcFolder); 7 } 8 9 // 递归删除目录 10 public void deleteFolder(File srcFolder) { 11 File[] fileArray = srcFolder.listFiles(); 12 if (fileArray != null) { 13 for (File file : fileArray) { 14 if (file.isDirectory()) { 15 deleteFolder(file); 16 } else { 17 System.out.println(file.getName() + ":" + file.delete()); 18 } 19 } 20 System.out.println(srcFolder.getName() + ":" + srcFolder.delete()); 21 } 22 } 23 } 24 25 public class TimerTest { 26 public static void main(String[] args) throws ParseException { 27 Timer t = new Timer(); 28 29 String s = "2014-11-27 15:45:00"; 30 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 31 Date d = sdf.parse(s); 32 33 t.schedule(new DeleteFolder(), d); 34 } 35 }
1:多线程有几种实现方案,分别是哪几种?
两种。
继承Thread类
实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。
2:同步有几种方式,分别是什么?
两种。
同步代码块
同步方法
3:启动一个线程是run()还是start()?它们的区别?
start();
run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法
4:sleep()和wait()方法的区别
sleep():必须指时间;不释放锁。
wait():可以不指定时间,也可以指定时间;释放锁。
5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而Object代码任意的对象,所以,定义在这里面。
6:线程的生命周期图
新建 -- 就绪 -- 运行 -- 死亡
新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡
建议:画图解释。