zoukankan      html  css  js  c++  java
  • JAVA 多线程学习总结

    新手一枚,Java学习中,把自己学习多线程的知识总结一下,梳理下知识,方便日后查阅,高手莫进。

    本文的主要内容:

    [1]    实现线程的两种方法                [2]  线程的启动与停止

    [3]     线程的互斥                           [4]  线程协作

    [5]   线程Join                                [6]Object实现生产者、消费者问题

    [7]Lock类实现生产者、消费者问题    [8] 线程优先级

    [9]守护线程(daemon线程)           [10]线程池概念

    一 实现线程的两种方法

    JAVA中实现线程,有两种方法。一种是 扩展 Threaad 类,一种是实现package java.lang.Runnble接口。

    首先来看看这JAVA API文档中如何定义这两个:

    Runnble接口

    package java.lang;
    public interface Runnable {
    public abstract void run();
    }

    这个接口的定义很简单,只是定义了一个方法。

    Threaad 类

    package java.lang;
    public
    class Thread implements Runnable {
    /* What will be run. */
        private Runnable target;
     private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc) {
        .........
            this.target = target;
        ........ 
        }
     public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
     public synchronized void start() {
        ........
    }
    
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    

      可以看出,Thread 也是实现了Runnable接口,用start()方法启动Therad()对象时,调用 实现Runnable接口所实现的run()方法。

    这个弄明白了,关于线程的启动与停止就很容易搞明白了。

    二、线程的启动与停止

    (1)启动一个线程必须调用Thread的start()方法,如果线程类继承Thread,则可以直接调用对象的start()方法启动,如果线程类实现的是Runnable接口,则需要将对象封装成Thread,再调用封装后对象的start();

    (2)JAVA只允许单继承,因此当一个类继承另一个类,又想成为一个线程时,就不能继承Thread类了,只能通过实现Runnable接口来实现。

    (3)线程的停止方法有

    [1] 让线程的run()方法执行完,线程自然结束。(这种方法最好)

    [2]通过轮询和共享标志位的方法来结束线程,例如while(flag){},flag的初始值设为真,当需要结束时,将flag的值设为false。(这种方法也不很     好,因为如果while(flag){}方法阻塞了,则flag会失效)

    [3]使用interrupt(),而程序会丢出InterruptedException例外,因而使得执行绪离开run()方法。

    [4]使用stop()方法终止线程,在上一个版本的JDK中,sun公司已经说明:Thread.stop, Thread.suspend and Thread.resume都已经不被推荐使用。因为会导致程序出现不可想象的状况。

    小例子如下:

     

     1 package com.beiyan.thread;
     2 
     3 public class ThreadStartStop {
     4 
     5     public static void main(String[] args) throws InterruptedException {
     6         ThreadStartStop startStop = new ThreadStartStop();
     7         ThreadA a = startStop.new ThreadA();// ThreadA和ThreadB
     8         ThreadB tb = startStop.new ThreadB(); // 是内部类,所以要先实例化主类,才能new两个线程对象
     9         a.start(); // ThreadA extends Thread 可以直接启动,
    10         Thread b = new Thread(tb);// ThreadB implements Runnable
    11         b.start(); // 必须要先实例化一个Thread 对象,在Thread 对象里面启动
    12 
    13         Thread.sleep(3000);// 主线程休息3秒
    14         a.StopThread();
    15         System.out.println("---------用 interupt()终止线程----------");
    16         ThreadC c = startStop.new ThreadC();
    17         c.start();
    18         c.interrupt();
    19 
    20     }
    21 
    22     class ThreadA extends Thread {
    23         private boolean running = false;
    24 
    25         // 重写Start方法
    26         public void start() {
    27             System.out.println("ThreadA启动");
    28             this.running = true;
    29             super.start();
    30         }
    31 
    32         // run方法
    33         public void run() {
    34 
    35             try {
    36                 while (running) {
    37                     Thread.sleep(4000);
    38                     System.out.println("ThreadA 执行中。。。");
    39 
    40                     // wait();
    41                 }
    42             } catch (InterruptedException e) {
    43                 System.out.println("ThreadA end!");
    44             }
    45         }
    46 
    47         // 自定义线程停止方法
    48         public void StopThread() {
    49             System.out.println("ThreadA Stop");
    50             running = false;
    51         }
    52     }
    53 
    54     class ThreadB implements Runnable {
    55 
    56         @Override
    57         public void run() {
    58 
    59             System.out.println("ThreadB执行中。。。");
    60             try {
    61                 Thread.sleep(1000);
    62             } catch (InterruptedException e) {
    63                 System.out.println("ThreadB Stop");
    64             }
    65             System.out.println("ThreadB 结束");
    66         }
    67 
    68     }
    69 
    70     class ThreadC extends Thread {
    71 
    72         // 重写Start方法
    73         public void start() {
    74             System.out.println("ThreadC启动");
    75             super.start();
    76         }
    77 
    78         // run方法
    79         public void run() {
    80 
    81             try {
    82                 while (!Thread.interrupted()) {
    83                     Thread.sleep(3000);
    84                     System.out.println("ThreadC 执行中。。。");
    85                     // wait();
    86                 }
    87             } catch (InterruptedException e) {
    88                 System.out.println("ThreadC end!");
    89             }
    90         }
    91 
    92     }
    93 }

     

     

    运行结果如下:

    ThreadA启动
    ThreadB执行中。。。
    ThreadB 结束
    ThreadA Stop
    ---------用 interupt()终止线程----------
    ThreadC启动
    ThreadA 执行中。。。

     可以看出:

    1、ThreadA是采用 flage标志位的方式终止线程的,最后一句结果是:ThreadA 执行中。。。当线程终止后,线程A还在继续执行,可见采用while(flag){}方法终止线程时,若线程出现Sleep()或者wait()阻塞时,并不能很好的结束线程。

    2、ThreadC是采用interrupt()方法终止线程的。程序会丢出InterruptedException异常,因而使得执行绪离开run()方法。

    3、线程ThreadA 和ThreadB 的启动方式。

    三、线程的互斥

      在操作系统中,有临界区,某些时刻只允许一个进程访问临界区。在Java中,对象也有临界区。在某些时候只允许一个线程访问临界区,称为线程的互斥。

    在类的方法中使用synchronized()关键字可以保证同一时刻只有一个线程进入该方法。

     1 public class Synchronized {
     2 
     3     private int money = 1000;
     4     private int money2 = 1000;
     5 
     6     // 用synchronized关键字定义一个同步的方法。
     7     public synchronized void add(int p) {
     8         System.out.println("已有money: " + money + "   存入: " + p + "  总共:" + (money + p));
     9         money += p;
    10     }
    11 
    12     // 没有使用synchronized
    13     public void add2(int p) {
    14         System.out.println("已有money2: " + money2 + "   存入: " + p + "  总共:" + (money2 + p));
    15         money2 += p;
    16     }
    17 
    18     public static void main(String[] args) {
    19         Synchronized sync = new Synchronized();
    20         // 没有使用synchronized,进行线程互斥
    21         ThreadTest2 p1 = new ThreadTest2(sync, 300);
    22         ThreadTest2 p2 = new ThreadTest2(sync, 200);
    23         ThreadTest2 p3 = new ThreadTest2(sync, 100);
    24         p1.start();
    25         p2.start();
    26         p3.start();
    27         // 使用synchronized,进行线程互斥
    28         ThreadTest t1 = new ThreadTest(sync, 300);
    29         ThreadTest t2 = new ThreadTest(sync, 200);
    30         ThreadTest t3 = new ThreadTest(sync, 100);
    31         t1.start();
    32         t2.start();
    33         t3.start();
    34     }
    35 
    36 }
    37 
    38 // 定义一个线程类,用于增加money
    39 class ThreadTest extends Thread {
    40     private Synchronized syn = null;
    41     private int moneyAdd = 0;
    42 
    43     public ThreadTest(Synchronized synchronized1, int money)
    44     {
    45         this.syn = synchronized1;
    46         this.moneyAdd = money;
    47     }
    48 
    49     public void run() {
    50         syn.add(moneyAdd);
    51     }
    52 }
    53 
    54 // 定义一个线程类,用于增加money2
    55 class ThreadTest2 extends Thread {
    56     private Synchronized syn = null;
    57     private int moneyAdd = 0;
    58 
    59     public ThreadTest2(Synchronized synchronized1, int money)
    60     {
    61         this.syn = synchronized1;
    62         this.moneyAdd = money;
    63     }
    64 
    65     public void run() {
    66         syn.add2(moneyAdd);
    67     }
    68 }

    运行结果如下:

    已有money2: 1000 存入: 300 总共:1300
    已有money2: 1000 存入: 200 总共:1200
    已有money2: 1000 存入: 100 总共:1100
    已有money: 1000 存入: 300 总共:1300
    已有money: 1300 存入: 200 总共:1500
    已有money: 1500 存入: 100 总共:1600

    money2是没有使用synchronized进行同步处理的。

    money是使用synchronized进行同步处理的。

    可见使用synchronized同步机制实现线程的互斥运行,从而避免出现错误。

    四、线程协作

    有时候多个线程需要协作,线程A往缓冲区里面写数据,线程B从缓冲区里读取数据,当缓冲区满时,线程A必须等待;当缓冲区为空时,线程B必须等待。

    在synchronized代码块里使用wait()方法,能够使当前线程进入等待状态,并释放当前线程用于的对象锁。

    在synchronized代码块里使用notify()或者notifyall()方法可以使当前线程释放对象锁,并唤醒其他正在等待该对象锁的线程。当有多个线程在等待该对象锁时,由Java虚拟机决定被唤醒的进程。

    例子如下

     1 package com.beiyan.thread;
     2 
     3 import java.util.Vector;
     4 
     5 public class WaitNotify {
     6     // 内部类,实现task的减少和增加
     7     class ThreadTask extends Thread {
     8         private Vector task = new Vector();
     9 
    10         public void run() {
    11             while (!Thread.interrupted()) {
    12                 synchronized (this) {
    13                     while (task.size() == 0) {
    14                         System.out.println("资源紧张,进入等待。。。");
    15                         try {
    16                             wait();
    17                             System.out.println("等待结束。。。");
    18                         } catch (InterruptedException e) {
    19                             // TODO Auto-generated catch block
    20                             e.printStackTrace();
    21                         }
    22                     }
    23                     try {
    24                         sleep(1000);
    25                         System.out.println("做完了一个任务");
    26                         task.remove(task.size() - 1);
    27                     } catch (InterruptedException e) {
    28                         // TODO Auto-generated catch block
    29                         e.printStackTrace();
    30                     }
    31                 }
    32             }
    33         }
    34 
    35         // 增加task 供使用
    36         public void addTask(String str) {
    37             synchronized (this) {
    38                 task.add(str);
    39                 System.out.println("增加了一个任务");
    40                 notify();
    41             }
    42         }
    43     }
    44 
    45     public static void main(String[] args) throws InterruptedException {
    46         WaitNotify notify = new WaitNotify();
    47         ThreadTask task = notify.new ThreadTask();
    48         task.start();
    49         Thread.sleep(1000);
    50         for (int i = 0; i < 3; i++) {
    51             task.addTask("str" + i);
    52             Thread.sleep(1000);
    53         }
    54 
    55     }
    56 }

    从例子可以看出,task线程中,调用start(),进而执行run()方法的内容,让没有资源(工作任务)时,task线程进入wait()状态,释放对线程对象的锁,这时main线程调用task的addTask()方法,使任务增加一个,调用notify()方法并通知线程,有资源可以继续执行。

    五、线程join

    有时候一个线程需要等待其他线程执行完毕后再进行操作,这是一种等待关系,也是一种协作。

    在当前线程中调用线程A的join(参数 b),表示:当前线程必须等待线程A执行长为 b 的时间再能继续执行。

    若参数为空,则表示 线程A执行完之后,当前线程才能继续执行。

    例子如下:

     1 package com.beiyan.thread;
     2 
     3 public class Join {
     4     class ThreadA extends Thread {
     5         private int id;
     6         private String name;
     7 
     8         public ThreadA(int a, String b)
     9         {
    10             this.id = a;
    11             this.name = b;
    12         }
    13 
    14         public void run() {
    15             int i = 0;
    16             while (i < 5) {
    17                 System.out.println(this.name + " 执行中。。。");
    18                 try {
    19                     Thread.sleep(500);
    20                 } catch (InterruptedException e) {
    21                     e.printStackTrace();
    22 
    23                 }
    24                 i++;
    25             }
    26         }
    27     }
    28 
    29     public static void main(String[] args) throws InterruptedException {
    30         Join join = new Join();
    31         ThreadA a = join.new ThreadA(1, "线程A");
    32         a.start();
    33         a.join(); // 线程A并入主线程,线程A执行完毕后,下面的for语句,才开始执行
    34 
    35         for (int i = 0; i < 5; i++) {
    36             System.out.println("Main Thread.....");
    37         }
    38     }
    39 }

    执行结果如下:

    线程A 执行中。。。
    线程A 执行中。。。
    线程A 执行中。。。
    线程A 执行中。。。
    线程A 执行中。。。
    Main Thread.....
    Main Thread.....
    Main Thread.....
    Main Thread.....
    Main Thread.....

    线程A join()主线程后,成为主线程的一部分,合并成一个线程,所以按顺序执行。

    六、Object实现生产者、消费者问题

      生产者,消费者问题是一个多线程协作问题,生产者负责生产产品,并存入仓库,消费者从仓库中获得产品并消费。

    例子如下:

     1 package com.beiyan.thread;
     2 
     3 import java.util.LinkedList;
     4 
     5 public class ProdutConsume extends Thread {
     6 
     7     private LinkedList<Object> wareHouse = new LinkedList<Object>();
     8     private final int MAX = 4;
     9 
    10     // 生产者内部类
    11     class Produt extends Thread {
    12         public void run() {
    13 
    14             while (!Thread.interrupted()) {
    15                 synchronized (wareHouse) {
    16                     try {
    17                         while (wareHouse.size() == MAX) {
    18                             System.out.println("仓库已满,正在等待消费");
    19                             wareHouse.wait();
    20 
    21                         }
    22                         Object obj = new Object();
    23                         if (wareHouse.add(obj)) {
    24                             System.out.println("生产一个新产品");
    25                             Thread.sleep((long) (Math.random() * 1000));
    26                             wareHouse.notify();
    27                         }
    28                     } catch (InterruptedException e) {
    29                         System.out.println("Producter Stop!!");
    30                     }
    31 
    32                 }
    33             }
    34 
    35         }
    36     }
    37 
    38     // 消费者内部类
    39     class Comsumer extends Thread {
    40 
    41         public void run() {
    42 
    43             while (!Thread.interrupted()) {
    44                 synchronized (wareHouse) {
    45                     try {
    46                         while (wareHouse.size() == 0) {
    47                             System.out.println("仓库为空,正在等待");
    48                             wareHouse.wait();
    49                         }
    50                         wareHouse.removeLast();
    51                         System.out.println("消费一个新产品");
    52                         Thread.sleep((long) (Math.random() * 500));
    53                         wareHouse.notify();
    54                     } catch (InterruptedException e) {
    55                         System.out.println("Comsumer Stop!!");
    56                     }
    57 
    58                 }
    59             }
    60 
    61         }
    62     }
    63 
    64     public static void main(String[] args) throws InterruptedException {
    65         ProdutConsume ps = new ProdutConsume();
    66         Produt produt = ps.new Produt();
    67         Comsumer comsumer = ps.new Comsumer();
    68         produt.start();
    69         comsumer.start();
    70 
    71     }
    72 }

    七、Lock类 实现生产者、消费者问题

      java JDK1.5版本后,出现了Condition。它用来替代传统的Object的wait()、notify()实现线程间的协作。使用Condition的await()、signal()这种方式实现线程间协作,比传统方式更加安全和高效。

      synchronized却只有一把锁,lock类可以有多把锁,比较灵活。对于生产者消费者问题,可以对仓库的满和空各设一把锁。

    举个例子:

    当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

      但是采用synchronized关键字来实现同步的话,就会导致一个问题:

      如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

      因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

     代码:

     1 package com.beiyan.thread;
     2 
     3 import java.awt.Container;
     4 import java.util.LinkedList;
     5 import java.util.concurrent.TimeUnit;
     6 import java.util.concurrent.locks.Condition;
     7 import java.util.concurrent.locks.Lock;
     8 import java.util.concurrent.locks.ReentrantLock;
     9 
    10 public class ProductConsumer2 extends Thread {
    11 
    12     private LinkedList<Object> wareHouse = new LinkedList<Object>();
    13     private final int MAX = 4;
    14     private final Lock lock = new ReentrantLock();
    15     private Condition full = lock.newCondition();
    16     private Condition empty = lock.newCondition();
    17 
    18     // 生产者内部类
    19     class Produt extends Thread {
    20         public void run() {
    21 
    22             while (!Thread.interrupted()) {
    23                 lock.lock();
    24                 try {
    25                     while (wareHouse.size() == MAX) {
    26                         System.out.println("仓库已满,正在等待消费");
    27                         full.await();
    28                     }
    29                     Object obj = new Object();
    30                     if (wareHouse.add(obj)) {
    31                         System.out.println("生产一个新产品");
    32                         Thread.sleep((long) (Math.random() * 1000));
    33                         empty.signal();
    34                     }
    35                 } catch (InterruptedException e) {
    36                     System.out.println("Producter Stop!!");
    37                 }
    38                 lock.unlock();
    39 
    40             }
    41         }
    42 
    43     }
    44 
    45     // 消费者内部类
    46     class Comsumer extends Thread {
    47 
    48         public void run() {
    49             while (!Thread.interrupted()) {
    50                 lock.lock();
    51                 try {
    52                     while (wareHouse.size() == 0) {
    53                         System.out.println("仓库为空,正在等待");
    54                         empty.await();
    55                     }
    56                     wareHouse.removeLast();
    57                     System.out.println("消费一个新产品");
    58                     Thread.sleep((long) (Math.random() * 500));
    59                     full.signal();
    60                 } catch (InterruptedException e) {
    61                     System.out.println("Comsumer Stop!!");
    62                 }
    63                 lock.unlock();
    64 
    65             }
    66 
    67         }
    68     }
    69 
    70     public static void main(String[] args) throws InterruptedException {
    71         ProductConsumer2 ps = new ProductConsumer2();
    72         Produt produt = ps.new Produt();
    73         Comsumer comsumer = ps.new Comsumer();
    74         produt.start();
    75         comsumer.start();
    76 
    77     }
    78 }

    运行结果如下:

    生产一个新产品
    生产一个新产品
    生产一个新产品
    生产一个新产品
    仓库已满,正在等待消费
    消费一个新产品
    消费一个新产品
    消费一个新产品
    消费一个新产品
    仓库为空,正在等待
    生产一个新产品
    生产一个新产品
    生产一个新产品
    生产一个新产品
    仓库已满,正在等待消费
    消费一个新产品
    消费一个新产品
    消费一个新产品
    消费一个新产品
    仓库为空,正在等待
    生产一个新产品
    生产一个新产品

    八、线程优先级

    与操作系统中的进程一样,Java线程也有优先级,在同等情况下,对于两个同时启动的线程,优先级高的先执行。

    Java线程优先级分为10个级别,数字越大,级别越高,默认为5;

    Thread 的setPriority()可以设置线程的优先级。

    代码如下:

     1 package com.beiyan.thread;
     2 
     3 public class Priority {
     4     public static void main(String[] args) {
     5         Priority priority = new Priority();
     6         ThreadPriority t1 = priority.new ThreadPriority(1);
     7         ThreadPriority t2 = priority.new ThreadPriority(2);
     8         System.out.println("线程t2的默认优先级: " + t2.getPriority());
     9         t1.setPriority(6);
    10         t1.start();
    11         t2.start();
    12     }
    13 
    14     class ThreadPriority extends Thread {
    15         private int id;
    16 
    17         public ThreadPriority(int a)
    18         {
    19             this.id = a;
    20         }
    21 
    22         public void run() {
    23             for (int i = 1; i < 5; i++) {
    24                 System.out.println("Thread " + id + " 正在执行。。。。");
    25                 try {
    26                     Thread.sleep((long) (Math.random() * 2000));
    27                 } catch (InterruptedException e) {
    28                     System.out.println("Thread " + id + " interrupt");
    29                 }
    30             }
    31         }
    32     }
    33 }

    执行结果:

    线程t2的默认优先级: 5
    Thread 1 正在执行。。。。
    Thread 2 正在执行。。。。
    Thread 1 正在执行。。。。
    Thread 1 正在执行。。。。
    Thread 1 正在执行。。。。
    Thread 2 正在执行。。。。
    Thread 2 正在执行。。。。
    Thread 2 正在执行。。。。

    线程1的优先级设置为6时,级别比线程2高,故线程1比线程2优先执行。

    九、守护线程

    有一种线程叫做守护线程(Daemon线程),如Java虚拟机的垃圾回收线程,它们在后台运行,为非守护线程提供服务。

    Thread的setDaemon实例方法设置线程是否为守护线程,参数为true表示该线程为守护线程。

    线程运行后,setDaemon实例方法无效,即必须在调用start()方法之前调用setDaemon方法。

    程序中启动的线程默认为非守护线程,但在守护线程中启动的线程都是守护线程。

    当程序中,所有的非守护线程都结束时,守护线程自动结束。

    例子如下:

     1 package com.beiyan.thread;
     2 
     3 public class Daemon {
     4 
     5     public static void main(String[] args) {
     6 
     7         Thread t1 = new MyCommon();
     8 
     9         Thread t2 = new Thread(new MyDaemon());
    10 
    11         t2.setDaemon(true); // 设置为守护线程
    12 
    13         t2.start();
    14 
    15         t1.start();
    16     }
    17 }
    18 
    19 //普通user线程
    20 class MyCommon extends Thread {
    21     public void run() {
    22 
    23         for (int i = 0; i < 4; i++) {
    24 
    25             System.out.println("用户第" + i + "次执行!");
    26 
    27             try {
    28 
    29                 Thread.sleep(100);
    30             }
    31 
    32             catch (InterruptedException e) {
    33 
    34                 e.printStackTrace();
    35             }
    36         }
    37 
    38     }
    39 }
    40 
    41 // 守护线程,守护线程要设置为无限循环,或者运行时间长一点,不然用户线程(非守护)还没结束,守护线程已经结束。
    42 class MyDaemon implements Runnable {
    43 
    44     public void run() {
    45 
    46         int i = 0;
    47         try {
    48             while (true) {
    49 
    50                 System.out.println("守护线程第" + i + "次执行!");
    51 
    52                 Thread.sleep(100);
    53 
    54                 i++;
    55 
    56             }
    57         } catch (
    58 
    59         InterruptedException e) {
    60 
    61             e.printStackTrace();
    62 
    63         } finally {
    64             System.out.println("守护线程结束");// 验证守护线程的finally是否执行
    65         }
    66 
    67     }
    68 }

    运行结果如下:

    用户第0次执行!
    守护线程第0次执行!
    守护线程第1次执行!
    用户第1次执行!
    守护线程第2次执行!
    用户第2次执行!
    守护线程第3次执行!
    用户第3次执行!
    守护线程第4次执行!

    可以看出,在普通用户线程结束运行后,虽然守护线程没执行完,但也立即结束。

    并且,守护线程finally()部分的代码是不执行的。

    学习异常时说,finall()最后必须执行的。但对于守护线程来说不适用。

    十 、线程池

    在进行socket网络编程时,往往需要多个服务器都能连接到服务器,服务器为每个连接到服务器的用户开你一个线程进行处理,假如连接到服务器上的用户非常多,那么服务器的线程就会越用越少,最好的解决办法是使用线程池。

    服务器启动时,分配何时数量的线程到一个线程池内,当有用户连上服务器之后,服务器从线程池中分配一个线程给用户,当线程池的线程分配完毕后,其他新连接的用户处于等待状态。

    当有用户与服务器断开连接后,服务器重新分配线程。

    具体例子放到下一篇Socket网络编程总结里面吧。

    最后,激励下自己:

    学习编程切忌浮躁,天亮后,面包会有的,牛奶会有的。

  • 相关阅读:
    gnuplot 让您的数据可视化
    sort
    sed
    AWK
    STA之RC Corner再论
    STA之RC Corner拾遗
    网络编程释疑之:TCP半开连接的处理
    Task 任务内部揭秘
    Task 线程任务
    【转】SQL Server、Oracle、MySQL和Vertica数据库常用函数对比
  • 原文地址:https://www.cnblogs.com/beiyan/p/4371820.html
Copyright © 2011-2022 走看看