线程和进程的概念
程序:是为完成特定任务,用某种语言编写的一组指令的集合。即一段静态的代码,静态对象。
进程:指在运行中的程序,程序一旦运行就是进程,同时进程也是是线程的容器,是系统进行资源分配和调度的单元,系统会在运行时为每个进程分配不同的内存取余,是资源分配的最小单元。是一个动态的过程:有它自身的产生,存在和消亡的过程。--生命周期
线程(thread)是操作系统能够进行运算调度和执行的最小单元。它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,是一个程序内部的一条执行路径,一个进程中可以并发多个线程,每条线程可以执行不同的任务。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)
多线程的创建
继承Thread类
 
  /**
  * 创建多线程的方式一:
  * 1.继承Thread类
  * 2.重写Thread类中的run()方法
  * 3.创建Thread类子类的对象
  * 4.通过此对象调用start()
  * 例子:遍历100000以内的所有偶数
  */
 public class ThreadTest {
     public static void main(String[] args) {
         MyThread m1 = new MyThread();
 
         m1.start();
 
         for (int i = 0; i < 100000; i++) {
             if(i%2 == 0){
                 System.out.println(i+"main");
             }
         }
     }
 
 }
 class MyThread extends Thread{
     @Override
     public void run() {
         for (int i = 0; i <100000 ; i++) {
             if(i%2 == 0){
                 System.out.println(i);
             }
         }
     }
 } 
 
会发现执行结果是相交的
…… 27250main 27252main 81768 81770 ……/** * 练习 * 创建俩个多线程,分别打印100以内的奇数和偶数 */ public class ThreadTest { public static void main(String[] args) { MyThread1 m1 = new MyThread1(); MyThread2 m2 = new MyThread2(); m1.start(); m2.start(); } } class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i <100 ; i++) { if(i%2 == 0){ System.out.println(Thread.currentThread().getName()+": "+i); } } } } class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i <100 ; i++) { if(i%2 == 1){ System.out.println(Thread.currentThread().getName()+": "+i); } } } }
运行结果
Thread-0: 0 Thread-1: 1 Thread-0: 2 Thread-1: 3 Thread-0: 4 Thread-1: 5 Thread-0: 6 Thread-1: 7 Thread-0: 8 Thread-1: 9 Thread-0: 10 Thread-1: 11 Thread-0: 12 Thread-1: 13 Thread-0: 14 Thread-1: 15 ……
实现Runnable接口
/** * 创建多线程的方式二: * 1.创建一个实现了Runnable接口的类 * 2.实现类去实现Runnable接口中的抽象方法:run() * 3.创建实现类的对象 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 * 5.通过Thread类的对象调用start() */ public class RunnableTest { public static void main(String[] args) { Run run=new Run(); Thread thread1 = new Thread(run); Thread thread2 = new Thread(run); thread1.start(); thread2.start(); } } class Run implements Runnable{ @Override public void run() { for (int i = 0; i < 100000; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } }//Thread類源码 public class Thread implements Runnable { private Runnable target; public Thread(Runnable target) { this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L); } public void run() { if (this.target != null) { this.target.run(); } } 
多线程实现窗口卖票的两种方式
public class WindowsTest { public static void main(String[] args) { Windows windows1 = new Windows(); Windows windows2 = new Windows(); Windows windows3 = new Windows(); windows1.setName("窗口1"); windows2.setName("窗口3"); windows3.setName("窗口2"); windows1.start(); windows2.start(); windows3.start(); } } class Windows extends Thread{  private static int ticket = 100;//注意static  @Override public void run() { while(true){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"卖票"+ticket); ticket--; } else { break; } } }  }
public class RunnableTest { public static void main(String[] args) { Run run=new Run(); Thread thread1 = new Thread(run); Thread thread2 = new Thread(run); Thread thread3 = new Thread(run); thread1.start(); thread2.start(); thread3.start(); } } class Run implements Runnable{ private int ticket=100;//这里不用加static @Override public void run() { while(true){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"卖票"+ticket); ticket--; }else { break; } } } }
多线程创建两种方式的比较
开发中:优先选择实现Runnable接口的方式
-  实现的方式没有单继承的局限性 
-  实现的方式更适合来处理多个线程共享数据 
联系:Thread继承了Runnable接口
相同点:都需要重写run方法,将线程执行的逻辑写在run方法中
JDK5.0新增线程创建方式
实现Callable接口
-  相比Runnable,可以有返回值 
-  方法可以抛出异常 
-  支持泛型的返回值 
-  需要借助FutureTask类,比如获取返回结果 
创建线程的方式三:
-  创建一个实现callable的接口 
-  实现call方法,将此线程需要执行的操作声明在call()中 
-  创建callable接口实现类的对象 
-  将此callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象 
-  将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象并调用start方法 
-  获取callable中call方法的返回值 
 
  public class CallableTest {
 
     public static void main(String[] args) {
         ThreadTests t = new ThreadTests();
         FutureTask futureTask1 = new FutureTask(t);
         new Thread(futureTask1).start();
         try {
             Object sum = futureTask1.get();
             System.out.println("总合"+sum);
         }catch (Exception e){
             e.printStackTrace();
         }
     }
 }
 
 class ThreadTests implements Callable{
     @Override
     public Object call() throws Exception {
       int num=0;
         for (int i = 0; i <= 100; i++) {
             if (i % 2 == 0){
                 System.out.println(i);
                 num += i;
             }
         }
             return num;
     }
 } 
 
线程池创建线程
优点:
-  提高响应速度(减少了创建新线程的时间) 
-  降低资源消耗(重复利用线程池中线程,不需要每次都创建) 
-  便于线程管理 
 
  public class ThreadPool {
     public static void main(String[] args) {
         ExecutorService service = Executors.newFixedThreadPool(10);
         service.execute(new NumberThreadTest());//适合Runnable接口
         service.execute(new NumberThreadTest());
         //service.submit()适合使用和callable
         service.shutdown();//关闭线程池
     }
 }
 class NumberThreadTest implements Runnable{
     private static int ticket=100;
     @Override
     public void run() {
         while(true){
             if (ticket>0){
                 System.out.println(Thread.currentThread().getName()+":"+ticket);
                 ticket--;
             }else {
                 break;
             }
         }
     }
 } 
 
线程常用方法
yield();//释放当前cpu的执行权
join();//在线程a中调用线程b的join方法,线程a会陷入阻塞状态直到线程b执行完毕stop();//强制线程生命期结束,不推荐使用boolean isAlive();//判断线程是否还或者sleep(long timemilltime);//让当前线程睡眠指定的milltime毫秒,在指定的milltime毫秒时间内,当前线程是阻塞状态
以下三个方法必须在同步代码块或者同步方法中使用,并且调用者必须是同步代码块或同步方法中的同步监视器(同一把锁)否则会出现IllegalMonitorStateException异常
属于Object类中的方法
wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,则唤醒优先级高的notifyAll():唤醒所有线程
线程的优先级
MAX_PRIORITY:10 MIN_PRIORITY:1 NORM_PRIORITY:5
默认优先级都为5
如何获取: 1. getPriority():获取线程的优先级 2. setPriority(int p):设置线程的优先级
并不是优先级高就一定先被CPU执行,只能从概率上讲更容易被CPU执行
线程的生命周期
-  新建 
-  就绪 
-  运行 
-  阻塞 
-  死亡 


synchronized
操作共享数据的代码,即为需要被同步的代码
-  共享数据:多个线程共同操作的变量 
-  同步监视器:锁 
-  任何一个类的对象都可以充当锁 
-  多个线程必须用同一把锁 
synchronized同步代码块解决线程安全问题
Runnable同步代码块解决线程安全的问题
 
  /**
  * 操作共享数据的代码,即为需要被同步的代码
  * 共享数据:多个线程共同操作的变量
  * 同步监视器:锁
  * 任何一个类的对象都可以充当锁
  * 多个线程必须用同一把锁
  */
 public class RunnableTest {
     public static void main(String[] args) {
         Run run=new Run();
         Thread thread1 = new Thread(run);
         Thread thread2 = new Thread(run);
         Thread thread3 = new Thread(run);
         thread1.start();
         thread2.start();
         thread3.start();
     }
 }
 class Run implements Runnable{
     private  int ticket=100;
     Object object=new Object();
     @Override
     public void run() {
 
         while(true){
             synchronized(object){//可以使用this充当锁,this为当前对象
                 if(ticket>0){
                     try {
                         Thread.sleep(200);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName()+"卖票"+ticket);
                     ticket--;
                 }else {
                     break;
                 }
             }
         }
     }
 } 
 
执行结果
Thread-0卖票100 Thread-0卖票99 Thread-2卖票98 Thread-1卖票97 Thread-1卖票96 Thread-1卖票95 Thread-2卖票94 Thread-2卖票93 Thread-2卖票92 Thread-2卖票91 Thread-2卖票90 Thread-2卖票89 Thread-2卖票88 Thread-2卖票87 Thread-2卖票86 Thread-2卖票85 Thread-0卖票84 Thread-0卖票83 Thread-0卖票82 Thread-0卖票81 Thread-0卖票80 Thread-0卖票79 Thread-0卖票78 Thread-0卖票77 Thread-0卖票76 Thread-0卖票75 Thread-0卖票74 Thread-0卖票73 Thread-0卖票72 Thread-0卖票71 Thread-0卖票70 Thread-0卖票69 Thread-0卖票68 Thread-0卖票67 Thread-0卖票66 Thread-0卖票65 Thread-0卖票64 Thread-0卖票63 Thread-0卖票62 Thread-0卖票61 Thread-0卖票60 Thread-2卖票59 Thread-2卖票58 Thread-2卖票57 Thread-2卖票56 Thread-2卖票55 Thread-2卖票54 Thread-2卖票53 Thread-2卖票52 Thread-2卖票51 Thread-2卖票50 Thread-2卖票49 Thread-2卖票48 Thread-2卖票47 Thread-2卖票46 Thread-2卖票45 Thread-2卖票44 Thread-2卖票43 Thread-1卖票42 Thread-1卖票41 Thread-1卖票40 Thread-1卖票39 Thread-1卖票38 Thread-1卖票37 Thread-1卖票36 Thread-1卖票35 Thread-1卖票34 Thread-1卖票33 Thread-1卖票32 Thread-1卖票31 Thread-1卖票30 Thread-1卖票29 Thread-1卖票28 Thread-1卖票27 Thread-1卖票26 Thread-2卖票25 Thread-2卖票24 Thread-2卖票23 Thread-2卖票22 Thread-2卖票21 Thread-2卖票20 Thread-2卖票19 Thread-2卖票18 Thread-2卖票17 Thread-2卖票16 Thread-2卖票15 Thread-2卖票14 Thread-2卖票13 Thread-2卖票12 Thread-2卖票11 Thread-2卖票10 Thread-2卖票9 Thread-2卖票8 Thread-2卖票7 Thread-2卖票6 Thread-2卖票5 Thread-2卖票4 Thread-2卖票3 Thread-0卖票2 Thread-0卖票1  Process finished with exit code 0
同步的方式解决了线程安全的问题,操作同步代码时,只有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低
Thread方式同步代码块解决线程安全的问题
 
  public class ThreadTest {
     public static void main(String[] args) {
         MyThread m1 = new MyThread();
         MyThread m2 = new MyThread();
         MyThread m3 = new MyThread();
         m1.start();
         m2.start();
         m3.start();
     }
 }
 class MyThread extends Thread{
     private static int ticket=100;
     Object object=new Object();
     @Override
     public void run() {
         while(true){
             synchronized(object){//不可以使用this充当锁,这里this代表了m1,m2,m3三个对象
                 try {
                     sleep(10);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 if(ticket > 0){
                     System.out.println(Thread.currentThread().getName()+"卖票:"+ticket);
                     ticket--;
                 }else {
                     break;
                 }
             }
 
         }
     }
 } 
 
执行结果
Thread-0卖票:100 Thread-1卖票:100 Thread-2卖票:100 Thread-2卖票:97 Thread-1卖票:97 Thread-0卖票:97 Thread-2卖票:94 Thread-0卖票:94 Thread-1卖票:94 Thread-1卖票:91 Thread-2卖票:91 Thread-0卖票:90 Thread-2卖票:88 Thread-1卖票:88 Thread-0卖票:88 Thread-2卖票:85 Thread-0卖票:84 Thread-1卖票:84 Thread-2卖票:82 Thread-0卖票:82 Thread-1卖票:82 Thread-2卖票:79 Thread-0卖票:79 Thread-1卖票:78 Thread-2卖票:76 Thread-1卖票:76 Thread-0卖票:76 Thread-2卖票:73 Thread-1卖票:73 Thread-0卖票:73 Thread-0卖票:70 Thread-1卖票:70 Thread-2卖票:70 Thread-1卖票:67 Thread-0卖票:67 Thread-2卖票:66 Thread-2卖票:64 Thread-1卖票:64 Thread-0卖票:62 Thread-2卖票:61 Thread-1卖票:61 Thread-0卖票:61 Thread-1卖票:58 Thread-0卖票:58 Thread-2卖票:56 Thread-0卖票:55 Thread-1卖票:55 Thread-2卖票:55 Thread-0卖票:52 Thread-1卖票:52 Thread-2卖票:52 Thread-1卖票:49 Thread-2卖票:49 Thread-0卖票:49 Thread-2卖票:46 Thread-0卖票:46 Thread-1卖票:46 Thread-2卖票:43 Thread-0卖票:43 Thread-1卖票:43 Thread-2卖票:40 Thread-1卖票:40 Thread-0卖票:40 Thread-0卖票:37 Thread-2卖票:37 Thread-1卖票:37 Thread-2卖票:34 Thread-1卖票:34 Thread-0卖票:34 Thread-2卖票:31 Thread-0卖票:31 Thread-1卖票:31 Thread-1卖票:28 Thread-2卖票:28 Thread-0卖票:28 Thread-0卖票:25 Thread-2卖票:25 Thread-1卖票:25 Thread-1卖票:22 Thread-0卖票:22 Thread-2卖票:22 Thread-2卖票:19 Thread-0卖票:19 Thread-1卖票:19 Thread-2卖票:16 Thread-1卖票:16 Thread-0卖票:16 Thread-2卖票:13 Thread-0卖票:13 Thread-1卖票:13 Thread-2卖票:10 Thread-0卖票:10 Thread-1卖票:10 Thread-2卖票:7 Thread-0卖票:7 Thread-1卖票:7 Thread-2卖票:4 Thread-1卖票:4 Thread-0卖票:4 Thread-1卖票:1 Thread-2卖票:1 Thread-0卖票:1  Process finished with exit code 0 
会发现还是线程不安全,这是因为三个对象使用的锁不是同一把锁了
一定要注意给object 加上static 才行
static Object object=new Object();
加入后就会发现线程又安全了
或者使用 MyThread.class 充当锁
Synchronized同步方法解决线程安全问题
使用同步方法解决线程安全问题
实现Runnable接口方式
 
  public class SynchronizedMethod {
     public static void main(String[] args) {
         ThreadMethod threadMethod = new ThreadMethod();
         Thread thread1 = new Thread(threadMethod);
         Thread thread2 = new Thread(threadMethod);
         Thread thread3 = new Thread(threadMethod);
         thread1.start();
         thread2.start();
         thread3.start();
     }
 }
 class ThreadMethod implements Runnable{
     private  int ticket=100;
     public synchronized void show(){//这里用的锁是this
         try {
             Thread.sleep(10);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         if(ticket > 0){
             System.out.println(Thread.currentThread().getName()+"卖票:"+ticket);
             ticket--;
         }
     }
 
     @Override
     public void run() {
         while(ticket>0){
             show();
         }
     }
 } 
 
继承Thread类方式
 
  public class SynchronizedMethodThread {
     public static void main(String[] args) {
         ThreadMethod threadMethod1 = new ThreadMethod();
         ThreadMethod threadMethod2 = new ThreadMethod();
         ThreadMethod threadMethod3 = new ThreadMethod();
         threadMethod1.start();
         threadMethod2.start();
         threadMethod3.start();
     }
 }
 class ThreadMethod extends Thread{
     private  static int ticket=100;
 
     public static synchronized void show(){//这里必须加上static,不加上static锁使用的为this对象,这里创建了三个对象。加上static锁使用的为ThreadMethod.class
         try {
             Thread.sleep(10);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         if(ticket > 0){
             System.out.println(Thread.currentThread().getName()+"卖票:"+ticket);
             ticket--;
         }
     }
     @Override
     public void run() {
         while(ticket>0){
             show();
         }
     }
 } 
 
同步方法总结:
-  同步方法仍涉及到同步监视器,只是不需要我们显示的声明 
-  非静态的同步方法,同步监视器(锁)是:this 
-  静态的同步方法,同步监视器(锁)是:当前类本身 
锁方式解决线程安全问题
public class ReentrantLockTest { public static void main(String[] args) { Window window = new Window(); Thread thread1 = new Thread(window); Thread thread2 = new Thread(window); Thread thread3 = new Thread(window); thread1.start(); thread2.start(); thread3.start(); } }  class Window implements Runnable{ private int ticket=100; private ReentrantLock reentrantLock=new ReentrantLock(); @Override public void run() { while (true){ reentrantLock.lock();//加锁 try { if(ticket>0){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖票"+ticket); ticket--; }else { break; } }finally { reentrantLock.unlock();//释放锁 } } } }注意:如果用继承Thread类的方式去使用lock
必须在 锁前面加上static
private static ReentrantLock reentrantLock=new ReentrantLock();
Synchronized和锁(Lock)方式的异同?
-  相同:两者都可以解决线程安全问题 
-  不同:Synchronized机制在执行完相应的同步代码后,自动释放同步监视器,Lock需要手动启动同步(Lock()),同时结束也需要手动结束同步(unlock()) 
单例模式线程安全
class Bank{ private static Bank instance = null; private Bank(){} public static Bank getInstance(){ // synchronized(Bank.class){//方式一:效率稍差 // if (instance == null){ // instance=new Bank(); // } // return instance; // } //方式二:效率更高 if (instance == null){ synchronized(Bank.class){ if (instance == null){ instance=new Bank(); } } } return instance; } }死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
死锁案例
public class DeadLock { public static void main(String[] args) { StringBuffer s1=new StringBuffer(); StringBuffer s2=new StringBuffer(); new Thread(){ @Override public void run() { synchronized (s1){ s1.append("a"); s2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start();  new Thread(new Runnable() { @Override public void run() { synchronized (s2){ s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1){ s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
多线程通信列题
 
  public class CommunicationTest {
     /**
      * 实现两个线程交替打印1-100
      */
     public static void main(String[] args) {
         Test test = new Test();
         Thread t1 = new Thread(test);
         Thread t2 = new Thread(test);
         Thread t3 = new Thread(test);
         t1.start();
         t2.start();
         t3.start();
     }
 }
 class Test implements Runnable{
     private int number=1;
     @Override
     public void run() {
             while (true){//while条件不能用number <= 100 因为这样就没有全锁住操作共享数据的代码,线程就不安全了
                 synchronized (this){     //也不能锁住while 锁住了就只有一个线程能进来操作了
                     if (number <= 100){
                         try {
                             Thread.sleep(20);
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                         System.out.println(Thread.currentThread().getName()+":"+number);
                         number++;
                     }else {
                         break;
                     }
                 }
             }
     }
 } 
 
 
  package com.atguigu.thread;
 
 public class CommunicationTest {
     /**
      * 线程通信的例子:实现两个线程交替打印1-100
      * 涉及到的方法:
      * wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
      * notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,则唤醒优先级高的
      * notifyAll():唤醒所有线程
      */
     public static void main(String[] args) {
         Test test = new Test();
         Thread t1 = new Thread(test);
         Thread t2 = new Thread(test);
         Thread t3 = new Thread(test);
         t1.start();
         t2.start();
         t3.start();
     }
 }
 
 class Test implements Runnable {
     private int number = 1;
 
     @Override
     public void run() {
         synchronized (this) {
             while (true) {
  // while条件不能用number <= 100
  // 因为这样就没有全锁住操作共享数据的代码,线程就不安全了
  // 也不能锁住while 锁住了就只有一个线程能进来操作了
                 //除非使用以下方法来操作线程
                 //先阻塞当前线程,然后再次循环时唤醒被阻塞的线程
                this.notify();//唤醒线程,这个this必须与sychronized锁住的对象相同
                 if (number <= 100) {
                     try {
                         Thread.sleep(20);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName() + ":" + number);
                     number++;
                     try {
                         wait();//当前线程阻塞,并释放同步监视器(锁)
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 } else {
                     break;
                 }
             }
         }
     }
 } 
 
sleep()和wait()方法的异同
-  相同点:一旦执行,都会让当前线程进入阻塞状态 
-  不同点: -  声明的位置不同,sleep声明在Thread类中,wait声明在Object类中。 
-  是否释放同步监视器,如果俩个方法都在同步代码块或同步方法中:sleep()不会释放锁,wait()会释放锁 
-  调用的要求不同:sleep可以在任何需要的场景下调用,wait必须在同步代码块或同步方法中使用。 
 
-  
线程通信的应用:经典例题: 生产者消费者问题
/** * 线程通信的应用:经典例题: 生产者消费者问题 */ public class ProduceConsumerTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); Consumer c1 = new Consumer(clerk); p1.start(); c1.start(); } } class Clerk{ private int product = 0;  public void produce() { while(true){  synchronized (this){ this.notify(); if(product < 20){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } product++; System.out.println(Thread.currentThread().getName()+"生产者生产产品"+product);  }else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }  public void consume() { while (true) { synchronized (this) { this.notify(); if (product > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } product--; System.out.println(Thread.currentThread().getName() + "消费者消费产品" + product); this.notify(); } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }  class Producer extends Thread{  private Clerk clerk;  public Producer(Clerk clerk){ this.clerk=clerk; } @Override public void run() { clerk.produce(); } }  class Consumer extends Thread{  private Clerk clerk;  public Consumer(Clerk clerk){ this.clerk=clerk; } @Override public void run() { clerk.consume(); } }欢迎加入学习交流群:166543371