ThreadPoolExecutor 概述:=====================================================================

构造函数:

4个构造函数, 其实最终都是调用了这个:

    /**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < ||
maximumPoolSize <= ||
maximumPoolSize < corePoolSize ||
keepAliveTime < )
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
} 其中:
int corePoolSize, 核心线程数, 个人任务 理解为最小的工作线程数 更好。
int maximumPoolSize, 最大工作线程数, 也就是最大pool size
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler

RejectedExecutionHandler 有这么几种:

AbortPolicy 直接抛出异常
CallerRunsPolicy 使用用户线程运行, CallerRunsPolicy其实保证了 无论怎么样,即使等待了很长时间,所以的工作任务都会被执行,而绝不会被舍弃。
DiscardOldestPolicy 舍弃工作的阻塞队列头的工作任务 ,the head of this queue ,然后重新运行当前任务。 也就是舍弃最 先进入队列的任务。 也就是最老的那个。
DiscardPolicy 直接舍弃,也就是忽略当前添加的工作任务

注意:
CallerRunsPolicy 是可能会影响 当前的用户线程的运行的。
AbortPolicy 可能会抛出给 用户 线程, 故。。 但是, 对之前添加的 Worker 无影响。

主要的属性或者方法:================================================================

addWorker 方法签名:private boolean addWorker(Runnable firstTask, boolean core) { 尝试为 原始线程 新创建 工作线程。
addWorkerFailed 新创建 工作线程怎么办?
advanceRunState
afterExecute 钩子
allowCoreThreadTimeOut() 是否允许核心线程超时? 默认是false, 可以通过allowCoreThreadTimeOut方法动态设置
allowsCoreThreadTimeOut 返回 allowCoreThreadTimeOut
awaitTermination 签名:public boolean awaitTermination(long timeout, TimeUnit unit) 等待线程池的状态变为TERMINATED,timeout内返回true,否则false。 接下来怎么办, 自己根据返回值去处理。
beforeExecute 钩子
checkShutdownAccess
compareAndDecrementWorkerCount cas方式增加工作线程的数目。一般来说是用在 新建工作线程成功的时候。
compareAndIncrementWorkerCount cas方式减少工作线程的数目。一般来说是用在 工作线程退出的时候。
ctlOf
decrementWorkerCount 无论如何,都要成功减少工作一个线程的数目, 只需一个。
drainQueue
ensurePrestart 即使corePoolSize is 0, 也要至少启动一个工作线程。
execute 关键的工作执行方法
finalize
getKeepAliveTime
interruptIdleWorkers(boolean onlyOne) 中断空闲的worker
interruptIdleWorkers() 调用前者
interruptWorkers 中断所有的worker。 Interrupts all threads, even if active. Ignores SecurityExceptions
isRunning 是否运行?
isRunningOrShutdown
onShutdown
prestartAllCoreThreads 预先启动所有core 线程。 这个方法用的少。 它会导致core 线程idly wait for work,默认是starting core threads only when new tasks are executed
prestartCoreThread 预先启动一个core 线程。 这个方法也用的少
processWorkerExit 钩子
purge
reject
remove
runStateAtLeast
runStateLessThan
runStateOf
runWorker 这个是被 Worker 调用的,是真正的 。。
setKeepAliveTime
shutdown 优雅关闭线程池
shutdownNow 强制关闭线程池
terminated
toString
tryTerminate 尝试Terminate ?
workerCountOf
allowCoreThreadTimeOut 是否允许核心线程超时
CAPACITY final 常量,固定为
COUNT_BITS
ctl
defaultHandler 默认reject handler
handler
keepAliveTime core 意外的线程的超时时间。 keepAliveTime其实完全可以理解为 keepAlived timeOut
mainLock 需要一个 JUC 的Lock, 为什么。
ONLY_ONE

// 几种状态
RUNNING
SHUTDOWN
STOP
TERMINATED
TIDYING

termination
shutdownPerm
workers 这个很关键, 它线程池是内部的 一个保留Worker 的缓存Set: 签名  private final HashSet<Worker> workers = new HashSet<Worker>();
workQueue

================================ IDEA 计算出来的属性 ================================
activeCount: int 实际正在运行 Worker 线程
completedTaskCount: long 所有总共完成的 "任务"。
corePoolSize: int 核心的(其实也就是最小的)Worker 线程数
largestPoolSize: int 曾经达到的最大的 实际运行的Worker 线程数
maximumPoolSize: int (潜在的)最大的 Worker 线程数
poolSize: int 当前线程池的 线程数, 不管线程是否正在运行 还是空闲
public BlockingQueue<Runnable> getQueue() {: BlockingQueue<Runnable> 阻塞队列
rejectedExecutionHandler: RejectedExecutionHandler 拒绝执行的策略
shutdown: boolean 是否关闭?
task: Runnable
taskCount: long
terminated: boolean
terminating: boolean
threadFactory: ThreadFactory
================================ IDEA 计算出来的属性 END ================================

内部类 Worker:

它是实际的工作线程:(其实是对原始线程或Runnable的封装)。 Worker 到底是什么东东? 它是这样的:

    private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable Worker
interruptIfStarted
lock
run
tryAcquire
tryLock
tryRelease
unlock
completedTasks
firstTask
serialVersionUID
thread
}

ThreadPoolExecutor 线程池 的个人理解 =====================================================================

线程池 最关键的两个属性: 工作线程的Set + 阻塞队列

private final HashSet<Worker> workers = new HashSet<Worker>(); // workers 的本质是保存所有的实际的工作的线程。

private final BlockingQueue<Runnable> workQueue; // workQueue 的本质是 对某个时刻 过多的任务 task 做缓冲。( 个人认为 缓冲 比缓存更合适此时的场景)

线程池本质上是 上面的两个属性:workers + workQueue 和 围绕它们的一些方法。

所以,JUC线程池,可以简单这么理解:
用户构造线程池,然后,随意的对线程池发出了各种各样的执行任务的请求,线程池检查 其本身两个属性:workers + workQueue。按照下面的规则处理任务请求:

1 如果当前pool size < corePoolSize, 则直接添加工作线程 Worker。
2 如果当前pool size >= corePoolSize,且 < maximumPoolSize,那么使用workQueue 缓冲之。 如果workQueue 无法再缓冲了,那么继续添加Worker
2 如果当前pool size >= maximumPoolSize,那么使用RejectedExecutionHandler 拒绝之。

Worker 运行完任务后,不会立即退出,而是立即从workQueue 获取task。获取也要分情况,如果当前pool size <= corePoolSize, 那么一直等待(可理解为阻塞)直到用户有新的工作任务请求发过来。 否则,当然就是pool size > corePoolSize 的情况, 那么就最多等待unit个keepAliveTime的时间,获取到了那么就立即运行,否则 就退出, 也就是工作线程退出,把 自己从workers 移除。

pool size 就是 当前Worker的总数,包括正在运行的和空闲的。

注意, 只有工作线程数已经达到了maximumPoolSize,而用户还在添加工作任务时, RejectedExecutionHandler 才会生效。

workQueue 的 缓冲能力 完全取决于workQueue的capacity,pool 能够同时接受的工作任务 == maximumPoolSize + workQueue的capacity 。
workers.size 的最大值 = maximumPoolSize;
也就是 : max pool size = maximumPoolSize + workQueue.size()

workQueue 的capacity可以是0(比如SynchronousQueue ),也就是 没有缓冲能力。 这个时候, 工作任务会直接被 之前创建的空闲Worker 执行(如果有的话) 或创建新的 Worker 执行。

keepAliveTime 直接影响了 Worker 的退出。 每个新建Worker 执行完第一个 工作任务后, 会循环去 workQueue 中拿被缓冲的 工作任务, 拿到则执行, 拿不到则等待一个 keepAliveTime 的时间。 所以, 如果 workQueue 中还有工作任务, 那么 Worker 是不会退出的。

源码分析: =====================================================================

线程从调用 ThreadPoolExecutor 的submit 方法开始, ThreadPoolExecutor 调用 AbstractExecutorService 的 submit:

    public Future<?> submit(Runnable task) {                    // command 就 工作任务
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null); // 创建一个 RunnableFuture
execute(ftask);
return ftask;
}

然后调用 execute :

    public void execute(Runnable command) {                     // command 的拒绝主要是在这个方法: execute完成的
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread // whether we should have added a thread 不太好理解。
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get(); // 我的理解: 这里应该是规则1, 如果工作线程数小于 核心线程数,直接增加线程。
if (workerCountOf(c) < corePoolSize) {//工作线程数 < 核心线程数 则 直接添加
if (addWorker(command, true)) // 成功则直接返回吧,但有可能添加失败,失败也是可能的,比如当前工作线程 == 核心线程数-1,同时有多个工作任务过来。
return;
c = ctl.get();
} // 这里应该是规则2, 如果工作线程数大于 核心线程数,小于最大线程数,则直接增加线程
if (isRunning(c) && workQueue.offer(command)) { //如果pool还在运行,那么尝试把 command 缓冲到workQueue 起来。
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))// recheck的意义在于: 虽然workQueue.offer 当前成功了, 但是同时,那个瞬间,pool 状态被改变为SHUTDOWN了,那么根据SHUTDOWN的语义,SHUTDOWN状态下是不应该再继续addWorker, 故先remove,然后再reject这个 command。
reject(command);
else if (workerCountOf(recheck) == ) // 如果还有工作线程,那么就不 addWorker, 因为 Worker 会自动从workQueue中拉取 工作任务; 否则就是表明没有任何工作线程了, 也就是工作线程 数 == 0, 那么 添加工作线程吧。
这种情况出现于 之前的工作任务耗时都很短, 很快运行完了, 故 没有进入 if (workerCountOf(c) < corePoolSize) 判断。。 也有可能 corePoolSize,但是workQueue有缓冲能力, 虽然,corePoolSize, 但是至少也是需要一个线程来运行 workQueue中的 工作任务的!。。
addWorker(null, false);
} // 规则3 体现在哪里呢? 如果工作线程数大于 最大线程数,则reject
else if (!addWorker(command, false))// 再次尝试 缓冲到workQueue, 注意这里的第二个参数是false。可能失败(比如workQueue已经满了。), 那么继续添加Worker吧; reject(command); // 如果工作线程数 >= 最大工作线程数,addWorker 就会成功,否则就失败, 失败则 reject 它
}

考虑一种情况:
若workQueue 没有缓冲能力, 而且corePoolSize也是0, 而且maximumPoolSize 也是0。No, 观察构造函数发现: 不可能出现 maximumPoolSize < corePoolSize
若workQueue 没有缓冲能力, 而且corePoolSize > 0,那么 工作任务其实 直接交给了工作线程处理。。

    private static boolean isRunning(int c) {    // 判断pool 状态是否是 小于SHUTDOWN。
return c < SHUTDOWN;
}

addWorker(null, false); 的含义:
addWorker 成功后是 运行工作线程, 然后调用下面的方法 runWorker :

    final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) { // getTask() 方法 会从 workQueue 中窃取工作任务。
w.lock(); // 为什么这里会有一个 lock ? 据说是为了防止其他pool 执行这个task, 匪夷所思。
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}

pool 中 线程name 的由来:=====================================================================

Thread 的 toString 是:

    public String toString() {
ThreadGroup group = getThreadGroup();
if (group != null) {
return "Thread[" + getName() + "," + getPriority() + "," +
group.getName() + "]";
} else {
return "Thread[" + getName() + "," + getPriority() + "," +
"" + "]";
}
}

而默认的线程工厂类是:

        DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
} public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}

可见,默认情况下,名字是: "pool-" + poolNumber.getAndIncrement() + "-thread-" + threadNumber.getAndIncrement()

那么pool.submit(thread); 时给 thread 设置的名字是无效的。 因为实际显示的时候, 不会用到,我们仅仅使用 thread 的 run 方法。 其实 pool 需要submit的并不是 Thread ,而是 Runnable 或者 Callable

pool 工作状态及 生命周期:=====================================================================

pool 状态有:

// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

pool 创建好之后,基本就是处于 RUNNING 了。 之后要是不执行 shutdown,shutdownNow, tryTerminate 方法, 它就一直处于 Running 状态。

shutdown 会优雅的关闭pool,它不限制时间,但最终pool 是会关闭的。
shutdownNow 立即强制的关闭 pool

tryTerminate 呢, 只是尝试关闭 pool ,而最终可能对于pool 没有影响?

AbstractExecutorService 提供了几个 重载的 submit 方法: =======================================

    /**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
} /**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) { // 对于这个方法, 返回值其实执行run 之前就已经固定了为 result
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
} /**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}

可见: submit 方法都是有返回值的, 当然, 对于Runnable 来说, 返回值只能是 null, 对于 Callable来说, 返回值类型是 其中call 的返回值类型。。
Runnable 其实也会被适配到 Callable。 实际上运行的是 Callable 的 run 方法。

submit 返回了一个Future 对象, 实际上我们还可以通过Future 对我们之前提交的任务进行一些操作, 比如检查是否执行完毕,取消它,等待执行完毕。

关于FutureTask =====================================================================

FutureTask 是RunnableFuture的一个实现:
public class FutureTask<V> implements RunnableFuture<V> {

newTaskFor(task); 返回的实际上就是 FutureTask。 FutureTask 对 Callable 和其返回值同时进行了封装,FutureTask运行的是 Callable run 方法。 FutureTask 提供get 方法, 获取Callable 的返回值。

FutureTask 的 get() 方法不会立即返回,它是异步的, 必须等待 任务运行完成了, 才会有结果,否则阻塞。

FutureTask 另外提供了一个 可超时的 get 方法: get(long timeout, TimeUnit unit):
public V get(long timeout, TimeUnit unit)

FutureTask 有7个状态:
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;

pool 的runWorker方法运行的实际是 FutureTask 的run 方法,最终运行 Callable 的run 方法:

THE END =====================================================================

参考
http://blog.csdn.net/vernonzheng/article/details/8299108 等

JUC 之 ThreadPoolExecutor 的一些研究的更多相关文章

  1. ThreadPoolExecutor源码解读

    1. 背景与简介 在Java中异步任务的处理,我们通常会使用Executor框架,而ThreadPoolExecutor是JUC为我们提供的线程池实现. 线程池的优点在于规避线程的频繁创建,对线程资源 ...

  2. Java 并发编程-不懂原理多吃亏(送书福利)

    作者 | 加多 关注阿里巴巴云原生公众号,后台回复关键字"并发",即可参与送书抽奖!** 导读:并发编程与 Java 中其他知识点相比较而言学习门槛较高,从而导致很多人望而却步.但 ...

  3. 动态线程池(DynamicTp)之动态调整Tomcat、Jetty、Undertow线程池参数篇

    大家好,这篇文章我们来介绍下动态线程池框架(DynamicTp)的adapter模块,上篇文章也大概介绍过了,该模块主要是用来适配一些第三方组件的线程池管理,让第三方组件内置的线程池也能享受到动态参数 ...

  4. 【JUC】JDK1.8源码分析之ThreadPoolExecutor(一)

    一.前言 JUC这部分还有线程池这一块没有分析,需要抓紧时间分析,下面开始ThreadPoolExecutor,其是线程池的基础,分析完了这个类会简化之后的分析,线程池可以解决两个不同问题:由于减少了 ...

  5. Java - "JUC线程池" ThreadPoolExecutor原理解析

    Java多线程系列--“JUC线程池”02之 线程池原理(一) ThreadPoolExecutor简介 ThreadPoolExecutor是线程池类.对于线程池,可以通俗的将它理解为"存 ...

  6. juc线程池原理(二):ThreadPoolExecutor的成员变量介绍

    概要 线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析ThreadPoolExecutor类,来了解线程池的原理. ThreadPoolExecutor数据结构 Thread ...

  7. JUC - Monitor监控ThreadPoolExecutor

    JUC - Monitor监控ThreadPoolExecutor 一个自定义Monitor监控ThreadPoolExecutor的执行情况 TASK WokerTask class WorkerT ...

  8. JUC - ThreadPoolExecutor

    JUC - ThreadPoolExecutor 创建一个ThreadPoolExecutor ThreadPoolExecutor( int corePoolSize, // 保留在池中的线程数,即 ...

  9. JUC源码分析-线程池篇(一):ThreadPoolExecutor

    JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...

随机推荐

  1. 在CAD二次开发中使用状态条按钮

    Pane pane = new Pane(); pane.Enabled = true; pane.Text = "状态条按钮"; pane.ToolTipText = " ...

  2. tomcat源码 Container

    1.Container的有四个子容器,分别是Engine,Host,Context,Wrapper,如下: 1.Engine:整个Catalina servlet引擎,标准实现为StandardEng ...

  3. Ubuntu 14.10 下安装rabbitvcs-版本控制

    在Windows下用惯了TortoiseSVN这只小乌龟,到了Ubuntu下很不习惯命令行的SVN,于是经过一番寻找安装了RabbitVCS这款SVN图形化前端工具(官方网站:http://rabbi ...

  4. 客户端负载均衡Ribbon之二:Loadbalance的源码

    Load Balance负载均衡是用于解决一台机器(一个进程)无法解决所有请求而产生的一种算法. 像nginx可以使用负载均衡分配流量,ribbon为客户端提供负载均衡,dubbo服务调用里的负载均衡 ...

  5. 解决wordpress文章归档和分类目录小工具标题重复问题

    最近更新了wordpress,发现更新后小工具中的文章归档和分类目录出现了标题重复,经检查,是部分主题下,主题的代码已经输出了标题,而wordpress的代码又再次输出了一次.于是我们需要删除word ...

  6. dspmq dspmqver command not found(dspmq命令找不到,dspmqver主安装目录设置不正确

    [root@rhv6-64b ~]# su - mqm -bash-4.1$ dspmq -bash: dspmq: command not found(dspmq命令找不到) -bash-4.1$ ...

  7. JVM调优常用参数

    JVM常用参数配置 -Xmx2048m 最大堆大小 -Xms1024m 初始堆大小 -Xmn1024m 年轻代大小 -XX:SurvivorRatio=8 Eden区与Survivor区的大小比值,设 ...

  8. 解决linux更新apt软件源时报出GPG错误

    今天给树莓派换源,爆出N个这错误: W: GPG error: http://mirrors.neusoft.edu.cn/raspbian/raspbian wheezy InRelease: Th ...

  9. (转)C#连接Oracle数据库(直接引用dll使用)

    原文地址:http://www.cnblogs.com/gguozhenqian/p/4262813.html 项目中有个功能需要从一台Oracle数据库获取数据,本以为是很简单的事情,直接将原来的S ...

  10. windows下使用gethostbyname函数报错无法解析的外部符号

    #include <winsock.h> 使用gethostbyname的函数的时候,会显示无法解析的外部符号. 主要问题是因为没有引用WS2_32的lib库 在include上面引用就行 ...