zoukankan      html  css  js  c++  java
  • java基础-多线程应用案例展示

                          java基础-多线程应用案例展示

                                            作者:尹正杰

    版权声明:原创作品,谢绝转载!否则将追究法律责任。

    一.两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候,一次性将蜂蜜吃光。

      1 /*
      2 @author :yinzhengjie
      3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
      4 EMAIL:y1053419035@qq.com
      5 */
      6 package cn.org.yinzhengjie.smallTestBullKnife;
      7 
      8 public class BeeDemo {
      9     public static void main(String[] args) {
     10         Box box = new Box();
     11         Bear b1 = new Bear(box , "熊大");
     12         Bear b2 = new Bear(box , "熊二");
     13         b1.start();
     14         b2.start();
     15         for(int i = 1 ; i <= 100 ; i ++){
     16             new Bee(box , "Bee" + i).start();
     17         }
     18     }
     19 }
     20 
     21 /**
     22  * 罐子
     23  */
     24 class Box{
     25     public static int MAX = 30 ;
     26     private int size = 0 ;
     27 
     28     /**
     29      * 添加蜂蜜方法
     30      */
     31     public synchronized void add(int cap){
     32         while((size + cap) > MAX){
     33             this.notifyAll();
     34             try {
     35                 this.wait();
     36             } catch (InterruptedException e) {
     37                 e.printStackTrace();
     38             }
     39         }
     40         size = size + cap ;
     41         this.notifyAll();
     42     }
     43 
     44     /**
     45      * 清空罐子
     46      */
     47     public synchronized int clear(){
     48         while(size < 20){
     49             this.notifyAll();
     50             try {
     51                 this.wait();
     52             } catch (InterruptedException e) {
     53                 e.printStackTrace();
     54             }
     55         }
     56         int temp = size ;
     57         size = 0 ;
     58         return temp ;
     59     }
     60 }
     61 
     62 /**
     63  * 生产者
     64  */
     65 class Bee extends Thread{
     66     private String beeName ;
     67     private Box box ;
     68     private int  production = 1;
     69     public Bee(Box box ,String name){
     70         this.beeName = name ;
     71         this.box = box ;
     72     }
     73 
     74     public void run() {
     75         while(true){
     76             box.add(production);
     77             System.out.println(beeName + "生产了" + production +"蜂蜜");
     78             try {
     79                 Thread.sleep(50);
     80             } catch (InterruptedException e) {
     81                 e.printStackTrace();
     82             }
     83         }
     84     }
     85 }
     86 
     87 /**
     88  * 消费者
     89  */
     90 class Bear extends Thread{
     91     private String bearName ;
     92     private Box box ;
     93     public Bear(Box box , String name){
     94         this.bearName = name ;
     95         this.box = box ;
     96     }
     97 
     98     public void run() {
     99         while(true){
    100 
    101             int size = box.clear();
    102             System.out.println(bearName + " 吃了【" + size +"】个蜂蜜");
    103         }
    104     }
    105 }

    二.有30个和尚,100个馒头,每个和尚最多吃4馒头,最少一个馒头,满足上述条件下,尽快把馒头吃没,使用多线程模拟,和尚就是线程。

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 package cn.org.yinzhengjie.smallTestBullKnife;
     7 
     8 /**
     9  * 和尚吃馒头问题
    10  */
    11 public class MonkDemo {
    12     public static void main(String[] args) {
    13         Basket b = new Basket();
    14         for(int i = 0 ; i < 30 ; i ++){
    15             new Monk(b , "和尚" + (i+1)+":	").start();
    16         }
    17     }
    18 }
    19 
    20 /**
    21  * 篮子,100个馒头
    22  */
    23 class Basket{
    24     //馒头数量==编号
    25     private int count = 100 ;
    26     //和尚数量 ==没吃馒头的和尚数量
    27     private int numMonks = 30 ;
    28     /**
    29      * 获取馒头的方法,返回馒头的编号
    30      */
    31     public synchronized int getBread(Monk monk){
    32         //和尚第一次吃
    33         if(monk.count == 0){
    34             int temp = count ;
    35             count -- ;
    36             numMonks -- ;
    37             return temp ;
    38         }
    39         //和尚还可以吃的情况
    40         else if(monk.count < Monk.MAX){
    41             //判断是否有多余的馒头
    42             if(count > numMonks){
    43                 int temp = count ;
    44                 count -- ;
    45                 return temp ;
    46             }
    47             else{
    48                 return -1 ;
    49             }
    50         }
    51         return -1 ;
    52     }
    53 }
    54 
    55 /**
    56  * 和尚
    57  */
    58 class Monk extends Thread{
    59     private String monkName ;
    60     private Basket basket ;
    61 
    62     //最少一个馒头
    63     public static int MIN = 1 ;
    64     //最多4个馒头
    65     public static int MAX = 4 ;
    66 
    67     //吃的馒头数
    68     public int count ;
    69 
    70 
    71     public Monk(Basket basket , String monkName){
    72         this.basket = basket ;
    73         this.monkName = monkName ;
    74     }
    75 
    76     public void run() {
    77         while(true){
    78             int no = basket.getBread(this);
    79             if(no ==-1){
    80                 break ;
    81             }
    82             else{
    83                 count ++ ;
    84                 System.out.println(monkName + "吃了编号为[" + no + "]的馒头");
    85             }
    86         }
    87         System.out.println(monkName + "共吃了【" + count + "】馒头");
    88     }
    89 }
      1 和尚2:    吃了编号为[100]的馒头
      2 和尚3:    吃了编号为[99]的馒头
      3 和尚3:    吃了编号为[96]的馒头
      4 和尚3:    吃了编号为[95]的馒头
      5 和尚3:    吃了编号为[94]的馒头
      6 和尚3:    共吃了【4】馒头
      7 和尚4:    吃了编号为[97]的馒头
      8 和尚4:    吃了编号为[93]的馒头
      9 和尚4:    吃了编号为[92]的馒头
     10 和尚4:    吃了编号为[91]的馒头
     11 和尚4:    共吃了【4】馒头
     12 和尚2:    吃了编号为[98]的馒头
     13 和尚2:    吃了编号为[90]的馒头
     14 和尚2:    吃了编号为[89]的馒头
     15 和尚2:    共吃了【4】馒头
     16 和尚6:    吃了编号为[88]的馒头
     17 和尚6:    吃了编号为[87]的馒头
     18 和尚6:    吃了编号为[86]的馒头
     19 和尚6:    吃了编号为[85]的馒头
     20 和尚6:    共吃了【4】馒头
     21 和尚1:    吃了编号为[84]的馒头
     22 和尚1:    吃了编号为[82]的馒头
     23 和尚8:    吃了编号为[83]的馒头
     24 和尚8:    吃了编号为[80]的馒头
     25 和尚8:    吃了编号为[79]的馒头
     26 和尚8:    吃了编号为[78]的馒头
     27 和尚8:    共吃了【4】馒头
     28 和尚1:    吃了编号为[81]的馒头
     29 和尚1:    吃了编号为[77]的馒头
     30 和尚1:    共吃了【4】馒头
     31 和尚5:    吃了编号为[76]的馒头
     32 和尚5:    吃了编号为[75]的馒头
     33 和尚12:    吃了编号为[74]的馒头
     34 和尚12:    吃了编号为[71]的馒头
     35 和尚12:    吃了编号为[70]的馒头
     36 和尚12:    吃了编号为[69]的馒头
     37 和尚12:    共吃了【4】馒头
     38 和尚14:    吃了编号为[68]的馒头
     39 和尚14:    吃了编号为[67]的馒头
     40 和尚14:    吃了编号为[66]的馒头
     41 和尚5:    吃了编号为[73]的馒头
     42 和尚5:    吃了编号为[64]的馒头
     43 和尚5:    共吃了【4】馒头
     44 和尚14:    吃了编号为[65]的馒头
     45 和尚14:    共吃了【4】馒头
     46 和尚10:    吃了编号为[72]的馒头
     47 和尚10:    吃了编号为[63]的馒头
     48 和尚10:    吃了编号为[62]的馒头
     49 和尚10:    吃了编号为[61]的馒头
     50 和尚10:    共吃了【4】馒头
     51 和尚9:    吃了编号为[60]的馒头
     52 和尚9:    吃了编号为[59]的馒头
     53 和尚7:    吃了编号为[58]的馒头
     54 和尚7:    吃了编号为[56]的馒头
     55 和尚7:    吃了编号为[55]的馒头
     56 和尚11:    吃了编号为[54]的馒头
     57 和尚11:    吃了编号为[52]的馒头
     58 和尚11:    吃了编号为[51]的馒头
     59 和尚9:    吃了编号为[57]的馒头
     60 和尚9:    吃了编号为[48]的馒头
     61 和尚9:    共吃了【4】馒头
     62 和尚11:    吃了编号为[50]的馒头
     63 和尚11:    共吃了【4】馒头
     64 和尚7:    吃了编号为[53]的馒头
     65 和尚15:    吃了编号为[45]的馒头
     66 和尚15:    吃了编号为[44]的馒头
     67 和尚15:    吃了编号为[42]的馒头
     68 和尚15:    吃了编号为[41]的馒头
     69 和尚15:    共吃了【4】馒头
     70 和尚17:    吃了编号为[46]的馒头
     71 和尚17:    吃了编号为[39]的馒头
     72 和尚19:    吃了编号为[38]的馒头
     73 和尚19:    吃了编号为[36]的馒头
     74 和尚19:    吃了编号为[35]的馒头
     75 和尚19:    吃了编号为[34]的馒头
     76 和尚19:    共吃了【4】馒头
     77 和尚13:    吃了编号为[47]的馒头
     78 和尚13:    吃了编号为[33]的馒头
     79 和尚13:    吃了编号为[32]的馒头
     80 和尚13:    吃了编号为[31]的馒头
     81 和尚13:    共吃了【4】馒头
     82 和尚20:    吃了编号为[49]的馒头
     83 和尚20:    吃了编号为[30]的馒头
     84 和尚20:    吃了编号为[29]的馒头
     85 和尚20:    吃了编号为[28]的馒头
     86 和尚20:    共吃了【4】馒头
     87 和尚22:    吃了编号为[26]的馒头
     88 和尚22:    吃了编号为[25]的馒头
     89 和尚22:    吃了编号为[23]的馒头
     90 和尚22:    吃了编号为[22]的馒头
     91 和尚22:    共吃了【4】馒头
     92 和尚17:    吃了编号为[37]的馒头
     93 和尚17:    吃了编号为[20]的馒头
     94 和尚17:    共吃了【4】馒头
     95 和尚18:    吃了编号为[40]的馒头
     96 和尚18:    吃了编号为[19]的馒头
     97 和尚18:    吃了编号为[18]的馒头
     98 和尚18:    吃了编号为[17]的馒头
     99 和尚18:    共吃了【4】馒头
    100 和尚25:    吃了编号为[16]的馒头
    101 和尚27:    吃了编号为[15]的馒头
    102 和尚27:    吃了编号为[13]的馒头
    103 和尚16:    吃了编号为[43]的馒头
    104 和尚16:    吃了编号为[11]的馒头
    105 和尚16:    吃了编号为[10]的馒头
    106 和尚16:    吃了编号为[8]的馒头
    107 和尚16:    共吃了【4】馒头
    108 和尚26:    吃了编号为[7]的馒头
    109 和尚7:    共吃了【4】馒头
    110 和尚26:    吃了编号为[6]的馒头
    111 和尚26:    吃了编号为[4]的馒头
    112 和尚26:    吃了编号为[3]的馒头
    113 和尚26:    共吃了【4】馒头
    114 和尚29:    吃了编号为[5]的馒头
    115 和尚30:    吃了编号为[2]的馒头
    116 和尚28:    吃了编号为[9]的馒头
    117 和尚28:    共吃了【1】馒头
    118 和尚27:    吃了编号为[12]的馒头
    119 和尚27:    共吃了【3】馒头
    120 和尚25:    吃了编号为[14]的馒头
    121 和尚25:    共吃了【2】馒头
    122 和尚24:    吃了编号为[21]的馒头
    123 和尚24:    共吃了【1】馒头
    124 和尚23:    吃了编号为[24]的馒头
    125 和尚21:    吃了编号为[27]的馒头
    126 和尚21:    共吃了【1】馒头
    127 和尚23:    共吃了【1】馒头
    128 和尚30:    共吃了【1】馒头
    129 和尚29:    吃了编号为[1]的馒头
    130 和尚29:    共吃了【2】馒头
    以上代码输出结果戳这里

    三.(熊吃蜂蜜升级版本)两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1到5不等,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候,一次性将蜂蜜吃光,蜜蜂向罐子中添加尽可能的蜂蜜,如果有剩余的话,下次继续添加剩余的量。

      1 /*
      2 @author :yinzhengjie
      3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
      4 EMAIL:y1053419035@qq.com
      5 */
      6 package cn.org.yinzhengjie.smallTestBullKnife;
      7 
      8 import java.util.Random;
      9 
     10 public class BeeproDemo {
     11     public static void main(String[] args) {
     12         BoxPro box = new BoxPro();
     13         Bear b1 = new Bear(box , "熊大");
     14         Bear b2 = new Bear(box , "熊二");
     15         b1.start();
     16         b2.start();
     17         for(int i = 1 ; i <= 100 ; i ++){
     18             new BeePro(  "Bee" + i,box).start();
     19         }
     20     }
     21 }
     22 
     23 /**
     24  * 罐子
     25  */
     26 class BoxPro{
     27     //定义管子容量的最大值为30
     28     public static int MAX = 30 ;
     29     //定义当前管子的容量
     30     private int size = 0 ;
     31 
     32     /**
     33      * 添加蜂蜜方法
     34      */
     35     public synchronized void add(int cap) {
     36         //判断添加的蜂蜜是否会使得管子变满
     37         if (cap > (MAX - size)){
     38             //获取添加蜂蜜多出的量
     39             int remain = cap + size -MAX;
     40             //如果添加的密封会是管子溢出的话,我们直接把罐子加满即可!
     41             size = MAX;
     42             //加满罐子后通知其他线程来消费,并让当前线程进入等待队列
     43             this.notifyAll();
     44             try {
     45                 this.wait();
     46                 Thread.sleep(500);
     47             } catch (Exception e) {
     48                 e.printStackTrace();
     49             }
     50             //如果当前线程被唤醒后,我们需要将之前剩余量继续进行添加操作!
     51             add(remain);
     52         }
     53         //如果当前的蜂蜜不能使罐子盛满,我们就直接添加蜂蜜即可!
     54         size+=cap;
     55         //加完之后需要通知其他线程
     56         this.notifyAll();
     57     }
     58 
     59     /**
     60      * 清空罐子
     61      */
     62     public synchronized int clear(){
     63         while(size < 20){
     64             this.notifyAll();
     65             try {
     66                 this.wait();
     67             } catch (InterruptedException e) {
     68                 e.printStackTrace();
     69             }
     70         }
     71         int temp = size ;
     72         size = 0 ;
     73         return temp ;
     74     }
     75 }
     76 
     77 /**
     78  * 生产者
     79  */
     80 class BeePro extends Thread{
     81     private String beeName ;
     82     private BoxPro box ;
     83     public BeePro(String name,BoxPro box){
     84         this.beeName = name ;
     85         this.box = box ;
     86     }
     87 
     88     public void run() {
     89         Random r = new Random();
     90         while(true){
     91             int production =r.nextInt(5) + 1;
     92             box.add(production);
     93             System.out.println(beeName + "生产了" + production +"蜂蜜");
     94         }
     95     }
     96 }
     97 
     98 /**
     99  * 消费者
    100  */
    101 class Bear extends Thread{
    102     private String bearName ;
    103     private BoxPro box ;
    104     public Bear(BoxPro box , String name){
    105         this.bearName = name ;
    106         this.box = box ;
    107     }
    108 
    109     public void run() {
    110         while(true){
    111             int size = box.clear();
    112             System.out.println(bearName + " 吃了【" + size +"】个蜂蜜");
    113         }
    114     }
    115 }

    四.两个售票员一起买100000张票,使用两种加锁方式(synchronize | ReentrantLock),看性能比对。

      1 /*
      2 @author :yinzhengjie
      3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
      4 EMAIL:y1053419035@qq.com
      5 */
      6 package cn.org.yinzhengjie.smallTestBullKnife;
      7 
      8 import java.util.concurrent.locks.ReentrantLock;
      9 
     10 public class SaleDemo {
     11     public static void main(String[] args) throws InterruptedException {
     12         TicketPool pool1 = new TicketPool();
     13         Thread[] ts = new Thread[4];
     14         long start = System.currentTimeMillis();
     15         for (int i=0;i<4;i++){
     16             Saler yzj = new Saler("yinzhengjie" + i, pool1, true);
     17             ts[i] = yzj;
     18             yzj.start();
     19         }
     20         for (Thread t : ts) {
     21             t.join();
     22         }
     23         long end = System.currentTimeMillis();
     24         System.out.printf("传统(normal)买票方式用时为:[%d]
    ",(end-start));
     25 
     26 
     27         TicketPool pool2 = new TicketPool();
     28         start = System.currentTimeMillis();
     29         for (int i=0;i<4;i++){
     30             Saler yzj = new Saler("yinzhengjie" + i, pool2, false);
     31             ts[i] = yzj;
     32             yzj.start();
     33         }
     34         for (Thread t : ts) {
     35             t.join();
     36         }
     37         end = System.currentTimeMillis();
     38         System.out.printf("轻量级(light)买票方式用时为:[%d]
    ",(end-start));
     39 
     40     }
     41 }
     42 
     43 /**
     44  * 定义票池
     45  */
     46 class TicketPool{
     47     private int Tickets = 100000;
     48 
     49 
     50     /**
     51      * 定义传统synchronized方式买票
     52      */
     53     public  int  doGetTickets1(){
     54         //判断是否符合买票的规则,如果票已经卖完了,就直接返回0.
     55         if (Tickets <= 0){
     56             return 0;
     57         }
     58         //使用synchronized方法同步代码块,保证原子性!
     59         synchronized(this){
     60             //定义当前票数的编号变量temp
     61             int temp = Tickets;
     62             //如果卖出去一张票,就将票数自减1.
     63             Tickets--;
     64             return temp;
     65         }
     66     }
     67 
     68     ReentrantLock lock = new ReentrantLock();
     69     public  int  doGetTickets2(){
     70         //判断是否符合买票的规则,如果票已经卖完了,就直接返回0.
     71         if (Tickets <= 0){
     72             return 0;
     73         }
     74         //上锁,知道写锁的代码块,同样也是保证原子性!
     75         lock.lock();
     76         //定义当前票数的编号变量temp
     77         int temp = Tickets;
     78         //如果卖出去一张票,就将票数自减1.
     79         Tickets--;
     80         //解锁
     81         lock.unlock();
     82         return temp;
     83     }
     84 }
     85 
     86 /**
     87  * 定义售票员类
     88  */
     89 class Saler extends Thread{
     90     private String saleName;
     91     private TicketPool pool;
     92     private boolean normal;
     93     /**
     94      *
     95      * @param saleName      //制定和售票员名称
     96      * @param pool          //指定票池
     97      * @param normal        //是都使用传统方式买票
     98      */
     99     public Saler(String saleName,TicketPool pool,boolean normal){
    100         this.saleName = saleName;
    101         this.pool = pool;
    102         this.normal = normal;
    103     }
    104 
    105     public void run() {
    106         while (true){
    107             int num = normal ? pool.doGetTickets1():pool.doGetTickets2();
    108             if (num == 0){
    109                 return;
    110             }
    111 //            System.out.printf("售票员[%s]出售了第【%d】票
    ",saleName,num);
    112 
    113         }
    114 
    115     }
    116 }
    117 
    118 /*
    119 以上代码输出结果如下:
    120 传统(normal)买票方式用时为:[9]
    121 轻量级(light)买票方式用时为:[8]
    122  */

     

    五.编写socket通信的MyServer,使用分线程完成和每个client的通信。

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 package cn.org.yinzhengjie.socket;
     7 
     8 import java.io.OutputStream;
     9 import java.net.InetSocketAddress;
    10 import java.net.ServerSocket;
    11 import java.net.Socket;
    12 
    13 public class MyServer {
    14     public static void main(String[] args) throws Exception {
    15         //服务器套接字
    16         ServerSocket ss = new ServerSocket(8888) ;
    17         while(true){
    18             //接受连接,
    19             System.out.println("正在接受连接.....");
    20             Socket sock = ss.accept();
    21             new CommThread(sock).start();
    22         }
    23     }
    24 }
    25 
    26 /**
    27  * 服务器和每个客户端的通信线程
    28  */
    29 class CommThread extends Thread{
    30     private Socket sock;
    31 
    32     public CommThread(Socket sock){
    33         this.sock = sock ;
    34     }
    35 
    36     public void run() {
    37         try {
    38             //获取远程地址和端口
    39             InetSocketAddress addr = (InetSocketAddress) sock.getRemoteSocketAddress();
    40             int port = addr.getPort();
    41             String ip = addr.getAddress().getHostAddress();
    42             System.out.printf("有人连接进来了!! : %s , %d
    ", ip, port);
    43 
    44             //向客户端发送消息
    45             int index = 0;
    46             OutputStream out = sock.getOutputStream();
    47             while (true) {
    48                 index ++ ;
    49                 out.write(("yinzhengjie" + index + "
    ").getBytes());
    50                 out.flush();
    51                 Thread.sleep(1000);
    52             }
    53         } catch (Exception e) {
    54             e.printStackTrace();
    55         }
    56     }
    57 }

      需要启动上述代码的服务端,才能用客户端进行连接操作  

    1>.使用java编写的客户端连接服务端

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 package cn.org.yinzhengjie.socket;
     7 
     8 import java.io.BufferedReader;
     9 import java.io.InputStream;
    10 import java.io.InputStreamReader;
    11 import java.net.Socket;
    12 
    13 public class MyClient {
    14     public static void main(String[] args) throws Exception {
    15         Socket s = new Socket("www.yinzhengjie.org.cn" ,8888) ;
    16         System.out.println("连接到服务器了!!");
    17         InputStream in = s.getInputStream();
    18         BufferedReader br = new BufferedReader(new InputStreamReader(in)) ;
    19         String line = null ;
    20         //获取服务端发来的消息
    21         while((line = br.readLine())!= null){
    22             System.out.println("收到消息 : " + line);
    23         }
    24     }
    25 }
    MyClient.java 文件内容

      测试结果如下:

      2>.使用Windows自带的telnet客户端连接服务端

     

      查看接收的数据信息如下:

      3>.使用xshell客户端连接服务端

      综上所述,有三个端连接了服务端,我们可以查看服务端的输出数据如下:

       上述的解决方案当客户端数量过多的话,最终可能会存在资源耗尽的情况,建议使用NIO技术,详情请参考:https://www.cnblogs.com/yinzhengjie/p/9257142.html。

  • 相关阅读:
    在WCF中使用Flag Enumerations
    WCF开发教程资源收集
    [转]WCF 4 安全性和 WIF 简介
    Asp.Net Web API 2 官网菜鸟学习系列导航[持续更新中]
    Asp.Net Web API 2第十八课——Working with Entity Relations in OData
    Asp.Net Web API 2第十七课——Creating an OData Endpoint in ASP.NET Web API 2(OData终结点)
    Asp.Net Web API 2第十六课——Parameter Binding in ASP.NET Web API(参数绑定)
    Asp.Net Web API 2第十五课——Model Validation(模型验证)
    函数 生成器 生成器表达式
    函数的进阶
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/9255629.html
Copyright © 2011-2022 走看看