zoukankan      html  css  js  c++  java
  • 10、线程池(重点)

    引用学习(狂神说)

    基本概念

    池化技术

    线程的运行,本质:是占用系统的资源!而 池化技术 => 优化资源的使用!

    一般会有:线程池、连接池、内存池、对象池(java的)....

    需要知道资源的 创建、销毁都是非常消耗资源的

    所以池化技术就是为了解决这个问题的

    用一句话简述池化技术:事先准备好一些资源,有人来要,就来我这里拿,用完之后还给我。

     

    池化技术的好处,以线程池为例,其他的池化技术都是相同的

    1、降低资源的消耗

    2、提高响应速度

    3、方便管理

    线程池:线程的复用、可以控制最大并发数、管理线程

     

    线程池的使用

    线程池的运行

    比如线程池最多有10个线程资源,同时被10个线程占用,此时线程资源已经用完。那么其他线程就会被拒绝,比如:丢弃掉(涉及到4种拒绝策略),为什么要这样呢?这就是线程池的功能:控制最大并发数,如果没有控制,一直有线程在运行占用,那么服务器就宕机了。

    线程池:有3大方法、7大参数、4中拒绝策略

    线程池:3大方法

    解析

    阿里手册规定,如下图所示:

    思考问题:

    1. 阿里手册为什么规定不允许使用Executors创建线程池呢?

    2. 推荐使用ThreadPoolExecutor创建线程池,而ThreadPoolExecutor又是什么呢?

    3. 上面提到的FixThreadPoolSingleThreadPoolCachedThreadPool又是什么呢?

    4. 提到的OOM是什么?

    接下来会用Executors创建线程池,既然不允许使用,为什么又要使用呢?

    因为做后面7大参数和4种策略的铺垫,并且解决上面的第三个问题。

     

    使用线程池必须知道:

    1. 线程池是怎么创建的?

      • Executors工具类创建的

    2. 如何使用线程池创建线程?

      • Executors工具类会创建3种线程池对象,这个对象ExecutorServiceexecute方法,可以丢入Runnable接口创建线程。

    3. 线程池使用完,怎么关闭?

      • ExecutorService对象中的shutdown()方法关闭

    4. 线程池使用完怎么才能办到一定会关闭呢?

      • 丢到try{}catch(){}finally{}的 finally 中就一定会关闭了

    5. 线程池不关闭会怎么样?

      • 程序会一直等待,等待有人来要线程

    方法一:SingleThreadPool单个线程的线程池

    • 线程池中的1个线程会被重复使用,不会有其他线程。
    package com.zxh.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    // Executors相当于工具类,比如像Collections
    // 线程池的创建方法:execute()
    public class Demo01 {
        public static void main(String[] args) {
            /*
                三大方法
             */
            ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程池
    //        Executors.newFixedThreadPool(5);    // 创建一个指定大小的线程池,指定容量为5个线程
    //        Executors.newCachedThreadPool();    // 可伸缩的,越强则强,遇弱则弱(就是线程少,就创建少量的线程池;线程多,就创建多数的线程池)
    
            try {
                for (int i = 0; i < 10; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    
    }

     方式二:FixedThreadPool指定大小的线程池

    • 只有线程池中指定数量的线程被使用
    package com.zxh.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    // Executors相当于工具类,比如像Collections
    // 线程池的创建方法:execute()
    public class Demo01 {
        public static void main(String[] args) {
            /*
                三大方法
             */
    //        ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程池
            ExecutorService threadPool = Executors.newFixedThreadPool(5);    // 创建一个指定大小的线程池,指定容量为5个线程
    //        Executors.newCachedThreadPool();    // 可伸缩的,越强则强,遇弱则弱(就是线程少,就创建少量的线程池;线程多,就创建多数的线程池)
    
            try {
                for (int i = 0; i < 10; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    
    }

    只会由线程池中的5个线程去处理

     方式三:CahceThreadPool可伸缩线程池

    • 根据要执行的线程数量,线程池会创建尽可能多的线程资源,供它们去拿
    package com.zxh.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    // Executors相当于工具类,比如像Collections
    // 线程池的创建方法:execute()
    public class Demo01 {
        public static void main(String[] args) {
            /*
                三大方法
             */
    //        ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程池
    //        ExecutorService threadPool = Executors.newFixedThreadPool(5);    // 创建一个指定大小的线程池,指定容量为5个线程
            ExecutorService threadPool = Executors.newCachedThreadPool();    // 可伸缩的,越强则强,遇弱则弱(就是线程少,就创建少量的线程池;线程多,就创建多数的线程池)
    
            try {
                for (int i = 0; i < 100; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    
    }

    有3个线程需要资源的情况,线程池中创建了3个线程资源

     

    有10个线程需要资源的情况,线程池中创建了8个线程,是遇强则强的

    有100个线程需要资源的情况,这边可以看到线程池中创建了37个线程资源

    线程池:7大参数

    7大参数是什么嘞?

    1、问题

    1. 阿里手册为什么规定不允许使用Executors创建线程池呢?

    2. 推荐使用ThreadPoolExecutor创建线程池,而ThreadPoolExecutor又是什么呢?

    3. 提到的OOM是什么?

    解决上面遗留下的问题。

    我们现在知道使用Executors工具类有三种方法创建线程池。

    2、分析一波Executors的源码

    • 查看Executors是如何创建线程的?
    // SingleThreadPool
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    // FixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    // CahcedThreadPool
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }    

    可以看出底层都是通过ThreadPoolExecutor创建的线程池

    那这个ThreadPoolExecutor底层又是怎么实现的呢?

     

    3、查看ThreadPoolExecutor底层源码

    1)进入这个对象的构造方法

    2)又可以看到调用了 this() 重载的构造器,点进去查看

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    3)发现了原本ThreadPoolExecutor构造器的模样,这个构造器就覆盖了线程池的7大参数和4种拒绝策略,接下来对7大参数进行解释。

    public ThreadPoolExecutor(int corePoolSize,        // 核心线程池数量
                              int maximumPoolSize,    // 最大核心线程池数量
                              long keepAliveTime,    // 比如:线程池中有50个线程,有40个线程在跑,那么还有10个县城是空闲的,这10个空闲线程如果在keepAliveTime时间过后还是空闲的(没有接到任务),就会释放掉这些空闲的线程
                              TimeUnit unit,        // 超时单位
                              BlockingQueue<Runnable> workQueue,    // 阻塞队列
                              ThreadFactory threadFactory,    // 线程工厂,用来创建线程的,一般不用动
                              RejectedExecutionHandler handler    // 拒绝策略) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
            null :
        AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

    4、进一步分析 7 大参数的概念

    概念图,比如银行办理业务,将银行看成是线程池

    1. 银行办理业务,在柜台上经常可以看到一些窗口在营业

      • 两个营业状态的窗口就是(核心线程池数量:2个)。

      • 指定了这个参数(核心线程池数量:2个),那么进来办理业务的两个人肯定就会去这两个窗口办理业务。

      • 对应创建线程就会使用线程中这两个线程资源。

    2. 如果现在进来第3个人办理业务,那么不会排队,而是拿号码之后去候客区等候

      • 这个候客区就是(阻塞队列这个参数)

      • 并且候客区只能容纳3个人(对应的阻塞队列也只能有3个人等待,这个在第10节中讲了)

      • 正在办理业务的人,结束后,队列中的人才会按顺序去办理业务。

    1. 现在候客区满人了,又进来第6个人,该怎么办呢?肯定的,那三个未营业的窗口就会开放,对应工作在这三个窗口的人也会被叫回来工作,这边都忙不过来了,还在放假呢...

      • 而柜台最多只能有5个窗口就是(最大核心线程池数量:5个)

      • 最大核心线程池数量 = 阻塞队列容量个数 + 核心线程数量

      • 只有办理业务的人过多(也就是线程数量过多)的时候,才会开放使用未营业状态的窗口(也就是处理核心资源线程之外的,另外3个核心线程资源)

      • 人不是很多,刚好可以容下那么多人,就不会去开放使用未营业的窗口。

      • 现在有一个问题:这个线程池最多可以处理多少个线程?就是柜台最多可以办理业务的窗口数(对应参数:最大核心线程池数量) + 候客区的座位数量(对应参数:阻塞队列容量)

    2. 银行正在办理业务的人和候客区的人都已经满了,还有人需要办理怎么办?让他站着吗..肯定不行,线程池必须控制最大并发数量,为了让服务器不宕机,正常运行。

      • 所以对再想要办理业务的人,使用拒绝策略。

      • 下面就涉及到了4种拒绝策略

    为什么不允许使用Executors创建线程池?

    • 解释了这么多,现在我们来看通过Executors创建的线程池,为什么不允许使用?
    • 上面在查看源码的时候,可以看到Executors在创建线程池中,对线程池7大参数中的几个参数使用了默认值:
    // SingleThreadPool
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    // FixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    // CahcedThreadPool
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }    
    
    // 使用到的默认值
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    /**
     * A constant holding the maximum value an {@code int} can
     * have, 2<sup>31</sup>-1.  2的31次方
     */
    @Native public static final int   MAX_VALUE = 0x7fffffff;    // 2的31次方

    1、首先看到阿里手册中的讲解

     

    2、通过Executors创建的 FixedThreadPoolSingleThreadPool这两个线程池,可能存在堆积大量的请求的情况,这个怎么看出来的。

    • 上面的源码中,,阻塞队列参数使用了new LinkedBlockingQueue<Runnable>()链表队列,而这个链表队列的构造器中设置了默认大小this(Integer.MAX_VALUE);,这个大小是2的31次方。

    • 在上面对7大参数解释中,说道这个阻塞队列参数,就是银行的候客区;这个候客区可以容纳2的31次方个人的话,但是又只有几个窗口可以处理业务,那得需要多久啊(对应服务器就会宕机)!对吧。

    • 这个new LinkedBlockingQueue<Runnable>()参数设置了允许请求的人(也就是办理业务的人)有2的31次方那么多,就会造成阿里手册中说的OOM(内存用完了)。

    3、通过Executors创建的 CachedThreadPool这个线程池,可能会创建大量线程,这个怎么看出来的?

    • Executors在对这个线程池创建的时候,最大核心线程池数量设置为Integer.MAX_VALUE,也就是说可以创建很多个线程,这个在线程池:3大方法中已经做过测试。

    • 所以也会导致OOM,内存用完了。

     

    线程池:4大策略

    问题:有4个拒绝策略呢?

    • 我们查看Executors在创建线程池的时候是怎么设置参数的。
    // 创建单个线程的线程池SingleThreadPool
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    // 接着往里面点,可以看到设置了Executors.defaultThreadFactory(), defaultHandler这两个参数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    // 查看变量defaultHandler,创建了 AbortPolicy 对象肯定就是拒绝策略的一种
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
    
    // 接着看 AbortPolicy实现了RejectedExecutionHandler接口
    public static class AbortPolicy implements RejectedExecutionHandler
        
    // 对应的接口,看一下它的实现类
    public interface RejectedExecutionHandler {
    
        void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
    }

    四大拒绝策略

     接下来对每一个策略进行测试

    • 手动创建一个线程池

    1、测试AbortPolicy

    package com.zxh.pool;
    
    import java.util.concurrent.*;
    
    public class DIYThreadPool {
        public static void main(String[] args) {
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    2,
                    5,
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(3),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );
            try {
                for (int i = 1; i <= 8; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    }

     很完美,使用5个线程资源,处理了8个线程。

    因为线程池最大承载 = 阻塞队列容量 + 最大核心线程池数量,所以接下来都是模拟9个线程,看一下是怎么操作第9个线程的

    • 也就是说:银行满了,还有人进来,不处理这个人,抛出异常

    2、测试CallerRunsPolicy

    • 哪里来的回去哪里!
      • 就是main让线程去获取线程池中的数据,就回到main线程中执行

    package com.zxh.pool;
    
    import java.util.concurrent.*;
    
    /**
     * 1、new ThreadPoolExecutor.AbortPolicy():银行满了,还有人进来,不处理这个人,抛出异常
     *      java.util.concurrent.RejectedExecutionException:拒绝执行例外异常
     * 2、new ThreadPoolExecutor.CallerRunsPolicy():哪里来的回去哪里!
     *      就是main让线程去获取线程池中的数据,就回到main线程中执行
     * 3、
     *
     * 4、
     */
    public class DIYThreadPool {
        public static void main(String[] args) {
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    2,
                    5,
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(3),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.CallerRunsPolicy()
            );
            try {
                // 因为线程池最大承载 = 阻塞队列容量 + 最大核心线程池数量,所以模拟9个线程,看一下是怎么操作第9个线程的
                for (int i = 1; i <= 9; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    }

    3、测试DiscardPolicy

    • 队列已满,直接丢弃,不会抛出异常

    package com.zxh.pool;
    
    import java.util.concurrent.*;
    
    /**
     * 1、new ThreadPoolExecutor.AbortPolicy():银行满了,还有人进来,不处理这个人,抛出异常
     *      java.util.concurrent.RejectedExecutionException:拒绝执行例外异常
     * 2、new ThreadPoolExecutor.CallerRunsPolicy():哪里来的回去哪里!
     *      就是main让线程去获取线程池中的数据,就回到main线程中执行
     * 3、new ThreadPoolExecutor.DiscardPolicy():队列已满,直接丢弃,不会抛出异常
     */
    public class DIYThreadPool {
        public static void main(String[] args) {
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    2,
                    5,
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(3),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardPolicy()
            );
            try {
                // 因为线程池最大承载 = 阻塞队列容量 + 最大核心线程池数量,所以模拟9个线程,看一下是怎么操作第9个线程的
                for (int i = 1; i <= 9; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK ");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    }

    4、测试DiscardOldestPolicy

    • 队列已满,尝试和最早的线程竞争,也不会抛出异常!

      • DiscardPolicy策略的升级版

      • 单词中有old,就是老嘛,和最早的进程竞争,怎么竞争呢?

      • 就是看最早的线程是不是块执行完毕了,如果执行完毕就马上跟上,如果还在执行就被丢弃了

      • 这个发生的情况,可能在更多线程下才会发生

    package com.zxh.pool;
    
    import java.util.concurrent.*;
    
    /**
     * 1、new ThreadPoolExecutor.AbortPolicy():银行满了,还有人进来,不处理这个人,抛出异常
     *      java.util.concurrent.RejectedExecutionException:拒绝执行例外异常
     * 2、new ThreadPoolExecutor.CallerRunsPolicy():哪里来的回去哪里!
     *      就是main让线程去获取线程池中的数据,就回到main线程中执行
     * 3、new ThreadPoolExecutor.DiscardPolicy():队列已满,直接丢弃,不会抛出异常
     * 4、new ThreadPoolExecutor.DiscardOldestPolicy():队列已满,尝试和最早的线程竞争,也不会抛出异常!
     *      DiscardPolicy策略的升级版
     *      单词中有old,就是老嘛,和最早的进程竞争,怎么竞争呢?
     *      就是看最早的线程是不是块执行完毕了,如果执行完毕就马上跟上,如果还在执行就被丢弃了
     *      这个发生的情况,可能在更多线程下才会发生
     */
    public class DIYThreadPool {
        public static void main(String[] args) {
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    2,
                    5,
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(3),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardOldestPolicy()
            );
            try {
                // 因为线程池最大承载 = 阻塞队列容量 + 最大核心线程池数量,所以模拟9个线程,看一下是怎么操作第9个线程的
                for (int i = 1; i <= 9; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK ");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    }

    小结与拓展

    线程池的最大大小该如何设置?

    了解:CPU密集型、IO密集型!(为了调优的)

    1)CPU密集型:

    方法:就是根据电脑CPU的逻辑处理器来设置。

    CPU的逻辑处理器是什么?

    你发现一个厨师的速度再怎么样也就只能达到这个速度了,但是并不能达到你的要求,那怎么办?你可以选择在找一个厨师,这样两个人一起工作的效率就提升了很多,这就是多核心处理器。(通俗说:表示可以同时开启的线程个数

    所以根据电脑的逻辑处理器个数来设置最大核心线程池数量,可以更好提高运行效率!

    • 线程池创建,根据电脑的逻辑处理器个数,设置最大核心线程池数量
    • 查看CPU的逻辑处理器3种方法,在第一节:1、多线程回顾中已经提到了
    package com.zxh.pool;
    
    import java.util.concurrent.*;
    
    public class DIYThreadPool {
        public static void main(String[] args) {
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    2,
                    Runtime.getRuntime().availableProcessors(),  // CPU的逻辑处理有8个
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(3),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardPolicy()
            );
            try {
                // 因为线程池最大承载 = 阻塞队列容量 + 最大核心线程池数量,所以模拟9个线程,看一下是怎么操作第12个线程的
                for (int i = 1; i <= 12; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + " OK ");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 线程池使用完毕后,必须关闭,怎么做到必须关闭呢?放到finally里面
                threadPool.shutdown();
            }
        }
    }

    2)IO密集型:

    1、问题:

    • 比如,一个程序,有15个大型的任务(线程),其中IO十分占用资源,那么该如何设置最大核心线程池数量?

    2、方法:

    • 需要判断你的程序中十分耗资源的线程,我只要大于>这个数就可以了,一般设置2倍;这样的话,即便有15个线程占用资源,还有一半的线程资源可以正常使用。

    致力于记录学习过程中的笔记,希望大家有所帮助(*^▽^*)!
  • 相关阅读:
    C# 线程手册 第二章 .NET 中的线程
    C# 线程手册 第一章 线程定义 .NET 和 C# 对线程的支持
    C# 线程手册 第二章 .NET 中的线程 创建一个线程
    C# 线程手册 第一章 线程定义 线程
    C# 线程手册 第二章 .NET 中的线程 时钟和回调
    C# 线程手册 第二章 .NET 中的线程 线程的生命周期
    C# 线程手册 第二章 .NET 中的线程 线程的优势
    Visual Entity 教程(一)从数据库创建模型
    Visual Entity 教程(三)添加 Attribute 到类或属性中
    Linq to Oracle 使用教程(二)创建实体类
  • 原文地址:https://www.cnblogs.com/zxhbk/p/12964663.html
Copyright © 2011-2022 走看看