当前位置:文档之家› 线程池编程

线程池编程

线程池编程
线程池编程

线程池编程

java.util.concurrent 多线程框架---线程池编程(一)

一般的服务器都需要线程池,比如Web、FTP等服务器,不过它们一般都自己实现了线程池,比如以前介绍过的Tomcat、Resin和Jetty等,现在有了JDK 5,我们就没有必要重复造车轮了,直接使用就可以,何况使用也很方便,性能也非常高。

1package concurrent;

2import java.util.concurrent.ExecutorService;

3import java.util.concurrent.Executors;

4public class TestThreadPool

5{

6public static void main(String args[]) throws InterruptedExcep tion

7 {

8// only two threads

9 ExecutorService exec = Executors.newFixedThreadPool(2); 10for(int index = 0; index < 100; index++)

11 {

12 Runnable run = new Runnable()

13 {

14public void run()

15 {

16long time = (long) (Math.random() * 1000);

17 System.out.println("Sleeping " + time + "ms"); 18try

19 {

20 Thread.sleep(time);

21

22 }

23catch (InterruptedException e)

24 {

25

26 }

27

28 }

29

30 };

31

32 exec.execute(run);

33

34 } // must shutdown

35

36 exec.shutdown();

37

38 }

39//}

40 }

41//}

42

上面是一个简单的例子,使用了2个大小的线程池来处理100个线程。但有一个问题:在for循环的过程中,会等待线程池有空闲的线程,所以主线程会阻塞的。为了解决这个问题,一般启动一个线程来做for循环,就是为了避免由于线程池满了造成主线程阻塞。不过在这里我没有这样处理。[重要修正:经过测试,即使线程池大小小于实际线程数大小,线程池也不会阻塞的,这与Tomcat的线程池不同,它将Runnable实例放到一个“无限”的BlockingQueue中,所以就不用一个线程启动for循环,Doug Lea果然厉害]

另外它使用了Executors的静态函数生成一个固定的线程池,顾名思义,线程池的线程是不会释放的,即使它是Idle。这就会产生性能问题,比如如果线程池的大小为200,当全部使用完毕后,所有的线程会继续留在池中,相应的内存和线程切换(while(true)+sleep循环)都会增加。如果要避免这个问题,就必须直

接使用ThreadPoolExecutor()来构造。可以像Tomcat的线程池一样设置“最大线程数”、“最小线程数”和“空闲线程keepAlive的时间”。通过这些可以基本上替换Tomcat的线程池实现方案。

需要注意的是线程池必须使用shutdown来显式关闭,否则主线程就无法退出。shutdown也不会阻塞主线程。

许多长时间运行的应用有时候需要定时运行任务完成一些诸如统计、优化等工作,比如在电信行业中处理用户话单时,需要每隔1分钟处理话单;网站每天凌晨统计用户访问量、用户数;大型超时凌晨3点统计当天销售额、以及最热卖的商品;每周日进行数据库备份;公司每个月的10号计算工资并进行转帐等,这些都是定时任务。通过java的并发库concurrent可以轻松的完成这些任务,而且非常的简单。

1package concurrent;

2import static java.util.concurrent.TimeUnit.SECONDS;

3import java.util.Date;

4import java.util.concurrent.Executors;

5import java.util.concurrent.ScheduledExecutorService;

6import java.util.concurrent.ScheduledFuture;

7

8public class TestScheduledThread

9{

10public static void main(String[] args)

11 {

12final ScheduledExecutorService scheduler = Executors .newS cheduledThreadPool(2);

13final Runnable beeper = new Runnable()

14 {

15int count = 0;

16public void run()

17 {

18 System.out.println(new Date() + "beep "+ (++coun t));

19 }

20 }; // 1秒钟后运行,并每隔2秒运行一次

21final ScheduledFuture beeperHandle = scheduler.scheduleAtF ixedRate( beeper, 1, 2, SECONDS);

22// 2秒钟后运行,并每次在上次任务运行完后等待5秒后重新运行

23final ScheduledFuture beeperHandle2 = scheduler .scheduleW ithFixedDelay(beeper, 2, 5, SECONDS);

24// 30秒后结束关闭任务,并且关闭Scheduler

25 scheduler.schedule(

26new Runnable()

27 {

28public void run()

29 {

30 beeperHandle.cancel(true);

31 beeperHandle2.cancel(true);

32 scheduler.shutdown();

33 }

34 }, 30, SECONDS);

35 }

36}

37

38

39

为了退出进程,上面的代码中加入了关闭Scheduler的操作。而对于24小时运行的应用而言,是没有必要关闭Scheduler的。

在实际应用中,有时候需要多个线程同时工作以完成同一件事情,而且在完成过程中,往往会等待其他线程都完成某一阶段后再执行,等所有线程都到达某一个阶段后再统一执行。

比如有几个旅行团需要途经深圳、广州、韶关、长沙最后到达武汉。旅行团中有自驾游的,有徒步的,有乘坐旅游大巴的;这些旅行团同时出发,并且每到一个目的地,都要等待其他旅行团到达此地后再同时出发,直到都到达终点站武汉。这时候CyclicBarrier就可以派上用场。CyclicBarrier最重要的属性就是参与者个数,另外最要方法是await()。当所有线程都调用了await()后,就表示这些线程都可以继续执行,否则就会等待。

package concurrent;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.concurrent.BrokenBarrierException;

import java.util.concurrent.CyclicBarrier;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

class TestCyclicBarrier

{

// 徒步需要的时间: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan public static int[] timeWalk = { 5, 8, 15, 15, 10 };

// 自驾游

public static int[] timeSelf = { 1, 3, 4, 4, 5 };

// 旅游大巴

public static int[] timeBus = { 2, 4, 6, 6, 7 };

public static String now()

{

SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

return sdf.format(new Date()) + ": ";

}

}

public class Tour implements Runnable

{

private int[] times;

private CyclicBarrier barrier;

private String tourName;

public Tour(CyclicBarrier barrier, String tourName, int[] times) {

this.times = times;

this.tourName = tourName;

this.barrier = barrier;

}

public void run()

{

try

{

Thread.sleep(times[0] * 1000);

System.out.println(TestCyclicBarrier.now() + tourName + " Reached Shenzhen");

barrier.await();

Thread.sleep(times[1] * 1000);

System.out.println(TestCyclicBarrier.now() + tourNam

e + " Reached Guangzhou");

barrier.await(); Thread.sleep(times[2] * 1000);

System.out.println(TestCyclicBarrier.now() + tourNam

e + " Reached Shaoguan");

barrier.await();

Thread.sleep(times[3] * 1000);

System.out.println(TestCyclicBarrier.now() + tourNam

e + " Reached Changsha");

barrier.await();

Thread.sleep(times[4] * 1000);

System.out.println(TestCyclicBarrier.now() + tourNam

e + " Reached Wuhan");

barrier.await();

}

catch (InterruptedException e)

{ }

catch (BrokenBarrierException e)

{ }

}

public static void main(String[] args)

{ // 三个旅行团

CyclicBarrier barrier = new CyclicBarrier(3);

ExecutorService exec = Executors.newFixedThreadPool(3);

exec.submit(new Tour(barrier, "WalkTour", TestCyclicBarrier.t imeWalk));

exec.submit(new Tour(barrier, "SelfTour", TestCyclicBarrier.t imeSelf));

exec.submit(new Tour(barrier, "BusTour", TestCyclicBarrier.ti meBus));

exec.shutdown();

}

}

运行结果:00:02:25: SelfTour Reached Shenzhen 00:02:25: BusTour Re ached Shenzhen 00:02:27: WalkTour Reached Shenzhen 00:02:30: Sel fTour Reached Guangzhou 00:02:31: BusTour Reached Guangzhou 0 0:02:35: WalkTour Reached Guangzhou 00:02:39: SelfTour Reached S haoguan 00:02:41: BusTour Reached Shaoguan

并发库中的BlockingQueue是一个比较好玩的类,顾名思义,就是阻塞队列。该类主要提供了两个方法put()和take(),前者将一个对象放到队列中,如果队列已经满了,就等待直到有空闲节点;后者从head取一个对象,如果没有对象,就等待直到有可取的对象。

下面的例子比较简单,一个读线程,用于将要处理的文件对象添加到阻塞队列中,另外四个写线程用于取出文件对象,为了模拟写操作耗时长的特点,特让线程睡眠一段随机长度的时间。另外,该Demo也使用到了线程池和原子整型(Ato micInteger),AtomicInteger可以在并发情况下达到原子化更新,避免使用了synchronized,而且性能非常高。由于阻塞队列的put和take操作会阻塞,为了使线程退出,特在队列中添加了一个“标识”,算法中也叫“哨兵”,当发现这个哨兵后,写线程就退出。

当然线程池也要显式退出了。

线程池--java.util.concurrent 多线程框架(二)

当然线程池也要显式退出了。

package concurrent;

import java.io.File;

import java.io.FileFilter;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.LinkedBlockingQueue;

import java.util.concurrent.atomic.AtomicInteger;

public class TestBlockingQueue {

static long randomTime() {

return (long) (Math.random() * 1000);

}

public static void main(String[] args) {

// 能容纳100个文件

final BlockingQueue queue = new LinkedBlockingQueue(100);

// 线程池

final ExecutorService exec = Executors.newFixedThreadPool(5);

final File root = new File(“F:\\JavaLib”);

// 完成标志

final File exitFile = new File(“”);

// 读个数

final AtomicInteger rc = new AtomicInteger();

// 写个数

final AtomicInteger wc = new AtomicInteger();

// 读线程

Runnable read = new Runnable() {

public void run() {

scanFile(root);

scanFile(exitFile);

}

public void scanFile(File file) {

if (file.isDirectory()) {

File[] files = file.listFiles(new FileFilter() { public boolean accept(File pathname) {

return pathname.isDirectory()

|| pathname.getPath().endsWith(“.java”);

}

});

for (File one : files)

scanFile(one);

} else {

try {

int index = rc.incrementAndGet();

System.out.println(“Read0: ”+ index + ”“+ file.getPath());

queue.put(file);

} catch (InterruptedException e) {

}

}

}

};

exec.submit(read);

// 四个写线程

for (int index = 0; index < 4; index++) {

// write thread

final int NO = index;

Runnable write = new Runnable() {

String threadName = “Write”+ NO;

public void run() {

while (true) {

try {

Thread.sleep(randomTime());

int index = wc.incrementAndGet();

File file = queue.take();

// 队列已经无对象

if (file == exitFile) {

// 再次添加”标志”,以让其他线程正常退出

queue.put(exitFile);

break;

}

System.out.println(threadName + “: ”+ index + ”“

+ file.getPath());

} catch (InterruptedException e) {

}

}

}

};

exec.submit(write);

}

exec.shutdown();

}

}

从名字可以看出,CountDownLatch是一个倒数计数的锁,当倒数到0时触发事件,也就是开锁,其他人就可以进入了。在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。

CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。

一个CountDouwnLatch实例是不能重复使用的,也就是说它是一次性的,锁一经被打开就不能再关闭使用了,如果想重复使用,请考虑使用CyclicBarrier。

下面的例子简单的说明了CountDownLatch的使用方法,模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。同样,线程池需要显式shutdown。

package concurrent;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class TestCountDownLatch {

public static void main(String[] args) throws InterruptedException { // 开始的倒数锁

final CountDownLatch begin = new CountDownLatch(1);

// 结束的倒数锁

final CountDownLatch end = new CountDownLatch(10);

// 十名选手

final ExecutorService exec = Executors.newFixedThreadPool(10);

for(int index = 0; index < 10; index++) {

final int NO = index + 1;

Runnable run = new Runnable(){

public void run() {

try {

begin.await();

Thread.sleep((long) (Math.random() * 10000));

System.out.println(“No.”+ NO + ” arrived”);

} catch (InterruptedException e) {

} finally {

end.countDown();

}

}

};

exec.submit(run);

}

System.out.println(“Game Start”);

begin.countDown();

end.await();

System.out.println(“Game Over”);

exec.shutdown();

}

}

运行结果:

Game Start

No.4 arrived

No.1 arrived

No.7 arrived

No.9 arrived

No.3 arrived

No.2 arrived

No.8 arrived

No.10 arrived

No.6 arrived

No.5 arrived

Game Over

有时候在实际应用中,某些操作很耗时,但又不是不可或缺的步骤。比如用网页浏览器浏览新闻时,最重要的是要显示文字内容,至于与新闻相匹配的图片就没有那么重要的,所以此时首先保证文字信息先显示,而图片信息会后显示,但又不能不显示,由于下载图片是一个耗时的操作,所以必须一开始就得下载。

Java的并发库的Future类就可以满足这个要求。Future的重要方法包括get()和cancel(),get()获取数据对象,如果数据没有加载,就会阻塞直到取到数据,而 cancel()是取消数据加载。另外一个get(timeout)操作,表示如果在timeo ut时间内没有取到就失败返回,而不再阻塞。

下面的Demo简单的说明了Future的使用方法:一个非常耗时的操作必须一开始启动,但又不能一直等待;其他重要的事情又必须做,等完成后,就可以做不重要的事情。

package concurrent;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

public class TestFutureTask {

public static void main(String[] args)throws InterruptedException,

ExecutionException {

final ExecutorService exec = Executors.newFixedThreadPool(5); Callable call = new Callable() {

public String call() throws Exception {

Thread.sleep(1000 * 5);

return “Other less important but longtime things.”;

}

};

Future task = exec.submit(call);

// 重要的事情

Thread.sleep(1000 * 3);

System.out.println(“Let’s do important things.”);

// 其他不重要的事情

String obj = task.get();

System.out.println(obj);

// 关闭线程池

exec.shutdown();

}

}

运行结果:

Let’s do important things.

Other less important but longtime things.

考虑以下场景:浏览网页时,浏览器了5个线程下载网页中的图片文件,由于图片大小、网站访问速度等诸多因素的影响,完成图片下载的时间就会有很大的不同。如果先下载完成的图片就会被先显示到界面上,反之,后下载的图片就后显示。

Java的并发库的CompletionService可以满足这种场景要求。该接口有两个重要方法:submit()和take()。submit用于提交一个runnable或者callable,一般会提交给一个线程池处理;而take就是取出已经执行完毕runnable或者c allable实例的Future对象,如果没有满足要求的,就等待了。 CompletionSe rvice还有一个对应的方法poll,该方法与take类似,只是不会等待,如果没有满足要求,就返回null对象。

package concurrent;

import java.util.concurrent.Callable;

import https://www.doczj.com/doc/d41876490.html,pletionService;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorCompletionService;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

public class TestCompletionService {

public static void main(String[] args) throws InterruptedException, ExecutionException {

ExecutorService exec = Executors.newFixedThreadPool(10); CompletionService serv =

new ExecutorCompletionService(exec);

for (int index = 0; index < 5; index++) {

final int NO = index;

Callable downImg = new Callable() {

public String call() throws Exception {

Thread.sleep((long) (Math.random() * 10000));

return “Downloaded Image ”+ NO;

}

};

serv.submit(downImg);

}

Thread.sleep(1000 * 2);

System.out.println(“Show web content”);

for (int index = 0; index < 5; index++) {

Future task = serv.take();

String img = task.get();

System.out.println(img);

}

System.out.println(“End”);

// 关闭线程池

exec.shutdown();

}

}

运行结果:

Show web content

Downloaded Image 1

Downloaded Image 2

Downloaded Image 4

Downloaded Image 0

Downloaded Image 3

End

操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java并发库的Semaphore可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,acquire()获取一个许可,如果没有就等待,而release()释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。Semaphore维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无限”的节点,用Semaphore可以实现有限大小的链表。另外重入锁ReentrantLock也可以实现该功能,但实现上要负责些,代码也要复杂些。

下面的Demo中申明了一个只有5个许可的Semaphore,而有20个线程要访问这个资源,通过acquire()和release()获取和释放访问许可。

package concurrent;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

public class TestSemaphore {

public static void main(String[] args) {

// 线程池

ExecutorService exec = Executors.newCachedThreadPool();

// 只能5个线程同时访问

final Semaphore semp = new Semaphore(5);

// 模拟20个客户端访问

for (int index = 0; index < 20; index++) {

final int NO = index;

Runnable run = new Runnable() {

public void run() {

try {

// 获取许可

semp.acquire();

System.out.println(“Accessing: ”+ NO);

Thread.sleep((long) (Math.random() * 10000));

// 访问完后,释放

semp.release();

} catch (InterruptedException e) {

}

}

};

exec.execute(run);

}

// 退出线程池

exec.shutdown();

}

}

运行结果:

Accessing: 0

Accessing: 1

Accessing: 2

Accessing: 3

Accessing: 4

Accessing: 5

Accessing: 6

Accessing: 7

Accessing: 8

Accessing: 9

Accessing: 10

Accessing: 11

Accessing: 12

Accessing: 13

Accessing: 14

Accessing: 15

Accessing: 16

Accessing: 17

Accessing: 18

Accessing: 19

java.util.concurrent 多线程框架---线程池编程(三)

1 引言

在软件项目开发中,许多后台服务程序的处理动作流程都具有一个相同点,就是:接受客户

端发来的请求,对请求进行一些相关的处理,最后将处理结果返回给客户端。这些请求的来源和方式可能会各不相同,但是它们常常都有一个共同点:数量巨大,处理时间短。这类服务器在实际应用中具有较大的普遍性,如web服务器,短信服务器,DNS服务器等等。因此,研究如何提高此类后台程序的性能,如何保证服务器的稳定性以及安全性都具有重要的实用价值。

2 后台服务程序设计

2.1 关于设计原型

构建服务器应用程序的一个简单的模型是:启动一个无限循环,循环里放一个监听线程监听某个地址端口。每当一个请求到达就创建一个新线程,然后新线程为请求服务,监听线程返回继续监听。

//简单举例如下:

import https://www.doczj.com/doc/d41876490.html,.*;

public class MyServer extends Thread{

public void run(){

try{

ServerSocket server=null;

Socket clientconnection=null;

server = new ServerSocket(8008);//监听某地址端口对

while(true){进入无限循环

clientconnection =server.accept();//收取请求

new ServeRequest(clientconnection).start();//启动一个新服务线程进行服务

……

}

}catch(Exception e){

System.err.println("Unable to start serve listen:"+e.getMessage());

e.printStackTrace();

}

}

}

实际上,这只是个简单的原型,如果试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显。

首先,为每个请求创建一个新线程的开销很大,为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源, 往往有时候要比花在处理实际的用户请求的时间和资源更多。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数。这样综合看来,系统的性能瓶颈就在于线程的创建开销。

其次,除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。在一个JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻运行的处理线程数目,以防止服务器被“压死”的情况发生。所以在设计后台程序的时候,一般需要提前根据服务器的内存、CPU等硬件情况设定一个线程数量的上限值。

如果创建和销毁线程的时间相对于服务时间占用的比例较大,那末假设在一个较短的时间内有成千上万的请求到达,想象一下,服务器的时间和资源将会大量的花在创建和销毁线程上,而真正用于处理请求的时间却相对较少,这种情况下,服务器性能瓶颈就在于创建和销毁线程的时间。按照这个模型写一个简单的程序测试一下即可看出,由于篇幅关系,此处略。如果把(服务时间/创建和销毁线程的时间)作为衡量服务器性能的一个参数,那末这个比值越大,服务器的性能就越高。

应此,解决此类问题的实质就是尽量减少创建和销毁线程的时间,把服务器的资源尽可能多地用到处理请求上来,从而发挥多线程的优点(并发),避免多线程的缺点(创建和销毁的

时空开销)。

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

3 JAVA线程池原理

3.1 原理以及实现

在实践中,关于线程池的实现常常有不同的方法,但是它们的基本思路大都是相似的:服务器预先存放一定数目的“热”的线程,并发程序需要使用线程的时候,从服务器取用一条已经创建好的线程(如果线程池为空则等待),使用该线程对请求服务,使用结束后,该线程并不删除,而是返回线程池中,以备复用,这样可以避免对每一个请求都生成和删除线程的昂贵操作。

一个比较简单的线程池至少应包含线程池管理器、工作线程、任务队列、任务接口等部分。其中线程池管理器(ThreadPool Manager)的作用是创建、销毁并管理线程池,将工作线程放入线程池中;工作线程是一个可以循环执行任务的线程,在没有任务时进行等待;任务队列的作用是提供一种缓冲机制,将没有处理的任务放在任务队列中;任务接口是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。下面的代码实现了创建一个线程池:

public class ThreadPool

{

private Stack threadpool = new Stack();

private int poolSize;

private int currSize=0;

public void setSize(int n)

{

poolSize = n;

}

public void run()

{

for(int i=0;i

4.2 框架与结构

下面让我们来看看util.concurrent的框架结构。关于这个工具包概述的e文原版链接地址是http: //https://www.doczj.com/doc/d41876490.html,/dl/cpjslides/util.pdf。该工具包主要包括三大部分:同步、通道和线程池执行器。第一部分主要是用来定制锁,资源管理,其他的同步用途;通道则主要是为缓冲和队列服务的;线程池执行器则提供了一组完善的复杂的线程池实现。

--主要的结构如下图所示

4.2.1 Sync

acquire/release协议的主要接口

- 用来定制锁,资源管理,其他的同步用途

- 高层抽象接口

- 没有区分不同的加锁用法

实现

-Mutex, ReentrantLock, Latch, CountDown,Semaphore, WaiterPreferenceSemaph

JAVA线程池原理333

在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.将需处理的任务的数量大 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销 2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。 线程池工作原理:

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。 线程池的替代方案 线程池远不是服务器应用程序内使用多线程的唯一方法。如同上面所提到的,有时,为每个新任务生成一个新线程是十分明智的。然而,如果任务创建过于频繁而任务的平均处理时间过短,那么为每个任务生成一个新线程将会导致性能问题。 另一个常见的线程模型是为某一类型的任务分配一个后台线程与任务队列。AWT 和 Swing 就使用这个模型,在这个模型中有一个 GUI 事件线程,导致用户界面发生变化的所有工作都必须在该线程中执行。然而,由于只有一个 AWT 线程,因此要在 AWT 线程中执行任务可能要花费相当长时间才能完成,这是不可取的。因此,Swing 应用程序经常需要额外的工作线程,用于运行时间很长的、同 UI 有关的任务。 每个任务对应一个线程方法和单个后台线程(single-background-thread)方法在某些情形下都工作得非常理想。每个任务一个线程方法在只有少量运行时间很长的任务时工作得十分好。而只要调度可预见性不是很重要,则单个后台线程方法就工作得十分好,如低优先级后台任务就是这种情况。然而,大多数服务器应用程序都是面向处理大量的短期任务或子任务,因此往往希望具有一种能够以低开销有效地处理这些任务的机制以及一些资源管理和定时可预见性的措施。线程池提供了这些优点。 工作队列 就线程池的实际实现方式而言,术语“线程池”有些使人误解,因为线程池“明显的”实现在大多数情形下并不一定产生我们希望的结果。术语“线程池”先于Java 平台出现,因此它可能是较少面向对象方法的产物。然而,该术语仍继续广泛应用着。 虽然我们可以轻易地实现一个线程池类,其中客户机类等待一个可用线程、将任务传递给该线程以便执行、然后在任务完成时将线程归还给池,但这种方法却存在几个潜在的负面影响。例如在池为空时,会发生什么呢?试图向池线程传递任务的调用者都会发现池为空,在调用者等待一个可用的池线程时,它的线程将阻塞。我们之所以要使用后台线程的原因之一常常是为了防止正在提交的线程被阻塞。完全堵住调用者,如在线程池的“明显的”实现的情况,可以杜绝我们试图解决的问题的发生。 我们通常想要的是同一组固定的工作线程相结合的工作队列,它使用 wait() 和

解决多线程中11个常见问题

并发危险 解决多线程代码中的11 个常见的问题 Joe Duffy 本文将介绍以下内容:?基本并发概念 ?并发问题和抑制措施 ?实现安全性的模式?横切概念本文使用了以下技术: 多线程、.NET Framework 目录 数据争用 忘记同步 粒度错误 读写撕裂 无锁定重新排序 重新进入 死锁 锁保护 戳记 两步舞曲 优先级反转 实现安全性的模式 不变性 纯度 隔离 并发现象无处不在。服务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。 与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。 这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。 这些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就开始执行并发操作,则不会遇到这种情况。本

文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。 首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。 当从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework 之类的程序)基本上都基于共享内存概念,进程中的所有线程均可访问驻留在同一虚拟地址空间中的数据。静态变量和堆分配可用于共享。请考虑下面这个典型的例子: static class Counter { internal static int s_curr = 0; internal static int GetNext() { return s_curr++; } } Counter 的目标可能是想为GetNext 的每个调用分发一个新的唯一数字。但是,如果程序中的两个线程同时调用GetNext,则这两个线程可能被赋予相同的数字。原因是s_curr++ 编译包括三个独立的步骤: 1.将当前值从共享的s_curr 变量读入处理器寄存器。 2.递增该寄存器。 3.将寄存器值重新写入共享s_curr 变量。 按照这种顺序执行的两个线程可能会在本地从s_curr 读取了相同的值(比如42)并将其递增到某个值(比如43),然后发布相同的结果值。这样一来,GetNext 将为这两个线程返回相同的数字,导致算法中断。虽然简单语句s_curr++ 看似不可分割,但实际却并非如此。 忘记同步 这是最简单的一种数据争用情况:同步被完全遗忘。这种争用很少有良性的情况,也就是说虽然它们是正确的,但大部分都是因为这种正确性的根基存在问题。 这种问题通常不是很明显。例如,某个对象可能是某个大型复杂对象图表的一部分,而该图表恰好可使用静态变量访问,或在创建新线程或将工作排入线程池时通过将某个对象作为闭包的一部分进行传递可变为共享图表。 当对象(图表)从私有变为共享时,一定要多加注意。这称为发布,在后面的隔离上下文中会对此加以讨论。反之称为私有化,即对象(图表)再次从共享变为私有。 对这种问题的解决方案是添加正确的同步。在计数器示例中,我可以使用简单的联锁: static class Counter { internal static volatile int s_curr = 0; internal static int GetNext() { return Interlocked.Increment(ref s_curr);

java深入理解线程池

深入研究线程池 一.什么是线程池? 线程池就是以一个或多个线程[循环执行]多个应用逻辑的线程集合. 注意这里用了线程集合的概念是我生造的,目的是为了区分执行一批应用逻辑的多个线程和 线程组的区别.关于线程组的概念请参阅基础部分. 一般而言,线程池有以下几个部分: 1.完成主要任务的一个或多个线程. 2.用于调度管理的管理线程. 3.要求执行的任务队列. 那么如果一个线程循环执行一段代码是否是线程池? 如果极端而言,应该算,但实际上循环代码应该算上一个逻辑单元.我们说最最弱化的线程池 应该是循环执行多个逻辑单元.也就是有一批要执行的任务,这些任务被独立为多个不同的执行单元.比如: int x = 0; while(true){ x ++; } 这就不能说循环中执行多个逻辑单元,因为它只是简单地对循环外部的初始变量执行++操作. 而如果已经有一个队列 ArrayList al = new ArrayList(); for(int i=0;i<10000;i++){ al.add(new AClass()); } 然后在一个线程中执行: while(al.size() != 0){ AClass a = (AClass)al.remove(0); a.businessMethod(); } 我们说这个线程就是循环执行多个逻辑单元.可以说这个线程是弱化的线程池.我们习惯上把这些相对独立的逻辑单元称为任务. 二.为什么要创建线程池? 线程池属于对象池.所有对象池都具有一个非常重要的共性,就是为了最大程度复用对象.那么 线程池的最重要的特征也就是最大程度利用线程. 从编程模型模型上说讲,在处理多任务时,每个任务一个线程是非常好的模型.如果确实可以这么做我们将可以使用编程模型更清楚,更优化.但是在实际应用中,每个任务一个线程会使用系统限入"过度切换"和"过度开销"的泥潭. 打个比方,如果可能,生活中每个人一辆房车,上面有休息,娱乐,餐饮等生活措施.而且道路交道永远不堵车,那是多么美好的梦中王国啊.可是残酷的现实告诉我们,那是不可能的.不仅每个人一辆车需要无数多的社会资源,而且地球上所能容纳的车辆总数是有限制的. 首先,创建线程本身需要额外(相对于执行任务而必须的资源)的开销.

完成端口加线程池技术实现

WinSock 异步I/O模型[5]---完成端口 - Completion Port ---------------------------------------------------------------------------- 如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了五种I/O模型,分别是: ■选择(select); ■异步选择(WSAAsyncSelect); ■事件选择(WSAEventSelect); ■重叠I/O(Overlapped I/O); ■完成端口(Completion Port) 。 每一种模型适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确,综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。 ============================================== █“完成端口”模型是迄今为止最复杂的一种 I/O 模型。但是,若一个应用程序同时需要管理很多的套接字, 那么采用这种模型,往往可以达到最佳的系统性能!但缺点是,该模型只适用于Windows NT 和 Windows 2000 以上版本的操作系统。 █因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多, 应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。 █从本质上说,完成端口模型要求我们创建一个 Win32 完成端口对象,通过指定数量的线程, 对重叠 I/O 请求进行管理,以便为已经完成的重叠 I/O 请求提供服务。

操作系统精髓与设计原理课后答案

操作系统精髓与设计原理课后答案 第1章计算机系统概述 1.1列出并简要地定义计算机的四个主要组成部分。 主存储器,存储数据和程序;算术逻辑单元,能处理二进制数据;控制单元,解读存储器中的指令并且使他们得到执行;输入/输出设备,由控制单元管理。 1.2定义处理器寄存器的两种主要类别。 用户可见寄存器:优先使用这些寄存器,可以使机器语言或者汇编语言的程序员减少对主存储器的访问次数。对高级语言而言,由优化编译器负责决定把哪些变量应该分配给主存储器。一些高级语言,如C语言,允许程序言建议编译器把哪些变量保存在寄存器中。 控制和状态寄存器:用以控制处理器的操作,且主要被具有特权的操作系统例程使用,以控制程序的执行。 1.3一般而言,一条机器指令能指定的四种不同操作是什么? 处理器-寄存器:数据可以从处理器传送到存储器,或者从存储器传送到处理器。 处理器-I/O:通过处理器和I/O模块间的数据传送,数据可以输出到外部设备,或者从外部设备输入数据。 数据处理:处理器可以执行很多关于数据的算术操作或逻辑操作。 控制:某些指令可以改变执行顺序。 1.4什么是中断? 中断:其他模块(I/O,存储器)中断处理器正常处理过程的机制。 1.5多中断的处理方式是什么? 处理多中断有两种方法。第一种方法是当正在处理一个中断时,禁止再发生中断。第二种方法是定义中断优先级,允许高优先级的中断打断低优先级的中断处理器的运行。 1.6内存层次的各个元素间的特征是什么? 存储器的三个重要特性是:价格,容量和访问时间。 1.7什么是高速缓冲存储器? 高速缓冲存储器是比主存小而快的存储器,用以协调主存跟处理器,作为最近储存地址的缓冲区。1.8列出并简要地定义I/O操作的三种技术。 可编程I/O:当处理器正在执行程序并遇到与I/O相关的指令时,它给相应的I/O模块发布命令(用以执行这个指令);在进一步的动作之前,处理器处于繁忙的等待中,直到该操作已经完成。 中断驱动I/O:当处理器正在执行程序并遇到与I/O相关的指令时,它给相应的I/O模块发布命令,并继续执行后续指令,直到后者完成,它将被I/O模块中断。如果它对于进程等待I/O的完成来说是不必要的,可能是由于后续指令处于相同的进程中。否则,此进程在中断之前将被挂起,其他工作将被执行。 直接存储访问:DMA模块控制主存与I/O模块间的数据交换。处理器向DMA模块发送一个传送数据块的请求,(处理器)只有当整个数据块传送完毕后才会被中断。 1.9空间局部性和临时局部性间的区别是什么? 空间局部性是指最近被访问的元素的周围的元素在不久的将来可能会被访问。临时局部性(即时间局部性)是指最近被访问的元素在不久的将来可能会被再次访问。 1.10开发空间局部性和时间局部性的策略是什么? 空间局部性的开发是利用更大的缓冲块并且在存储器控制逻辑中加入预处理机制。时间局部性的开发是利用在高速缓冲存储器中保留最近使用的指令及数据,并且定义缓冲存储的优先级。 第2章操作系统概述

普元EOS工作流引擎设计原理

EOS工作流引擎工作原理 作者:Gocom注册用户dogreet(李国生) 1. 工作流基础知识 ……略 2. EOS工作流引擎工作原理 本文是我在工作之余写的一点我对EOS工作流的了解,我的理解不一定全是对的,可能会与引擎的真正的面目有出入。所以只能提供给大家一点参考。 2.1. EOS工作流引擎核心调度算法 EOS工作流最重要的组成部分是它的核心调度算法,在我们没有深入研究它的工作原理之前我们认为它的工作原理是在工作项,活动和流程实例对象上加了一些标志位来驱动流程的运转。认为其引擎完全是个由数据库来驱动流程的引擎(安徽二期的工作流平台好象就是以库表来驱动流程的运转),其实它是由事件来驱动流程运转的引擎,数据库只是把引擎运转前后的状态持久化。在我近来在工作之余对其引擎的工作原理进行跟踪才弄明白在EOS帮助文档上介绍的“事件驱动”的工作流引擎。 2.1.1. EOS工作流引擎的事件类型

以上的每个事件都是原子的不可分割的。其中一系列事件的集合通过EOS引擎事件调度机制实现我们平时在工作中经常遇到的如启动流程,结束工作项等等。(在事件类型类中EOS定义了29种事件,但在事件工厂类中EOS定义了26种类型。) 1.1.1. EOS工作流事件调度机制 EOS事件的调度服务是在工作流引擎初始化时通过服务工厂类加载到内存中(ServiceFactory.initEventService())。用户可以通过服务工厂类(ServiceFactory)取得JVM的唯一事件服务实例进行事务调度。所有的事件程序入口都是事件类(EventService),这个类其实是个接口,其有两个实现类,一个是单线程的实现类SingleThreadEventService (在实现代码中其实不是单线程,而是单例的对象),一个是多线程的实现类MulThreadThreadSvc,(其实现方式不在这里详细说明,多线程的类后面又跟了一大堆的线程池实现代码),在事件服务类中有一个属性类是WFEventDisposer,这个类包含了事

使用 GPars 解决常见并发问题

重庆IT论坛https://www.doczj.com/doc/d41876490.html, 在并发性时代,带有4、6 和16 个处理器核心的芯片变得很普遍,而且在不久的将来,我们会看到带有上百甚至上千个核心的芯片。这种处理能力蕴含着巨大的可能性,但对于软件开发人员来说,它也带来了挑战。最大限度地利用这些闪耀新核的需求推动了对并发性、状态管理和为两者构建的编程语言的关注热潮。 Groovy、Scala 和Clojure 等JVM 语言满足了这些需求。这三种都是较新的语言,运行于高度优化的JVM 之上,可以使用Java 1.5 中新增的强大的Java 并发库。尽管每种语言基于其原理采用不同的方法,不过它们都积极支持并发编程。 在本文中,我们将使用GPars,一种基于Groovy 的并发库,来检查模型以便解决并发性问题,比如后台处理、并行处理、状态管理和线程协调。 为何选择Groovy ?为何选择GPars ? Groovy 是运行于JVM 之上的一种动态语言。基于Java 语言,Groovy 移除了Java 代码中的大量正式语法,并添加了来自其他编程语言的有用特性。Groovy 的强大特性之一是它允许编程人员轻松创建基于Groovy 的DSL。(一个DSL 或域特定语言是一种旨在解决特定编程问题的脚本语言。参阅参考资料了解有关DSL 的更多信息。) 获取代码和工具 参阅参考资料部分下载Groovy、GPars 和本文中用到的其他工具。您可以随时下载本文的可执行代码样例。 GPars 或Groovy Parallel Systems 是一种Groovy 并发库,捕捉并发性和协调模型作为DSL。GPars 的构思源自其他语言的一些最受欢迎的并发性和协调模型,包括:?来自Java 语言的executors 和fork/join ?来自Erlang 和Scala 的actors ?来自Clojure 的agents ?来自Oz 的数据流变量 Groovy 和GPars 的结合成为展示各种并发性方法的理想之选。甚至不熟悉Groovy 的Java 开发人员也能轻松关注相关讨论,因为Groovy 的语法以Java 语言为基础。本文中的示例基于Groovy 1.7 和GPars 0.10。 回页首 后台和并行处理 一个常见的性能难题是需要等待I/O。I/O 可能涉及到从一个磁盘、一个web 服务或甚至是一名用户读取数据。当一个线程在等待I/O 的过程中被阻止时,将等待中的线程与原始执行线程分离开来将会很有用,这将使它能继续工作。由于这种等待是在后台发生的,所以我们称这种技术为后台处理。 例如,假设我们需要这样一个程序,即调用Twitter API 来找到针对若干JVM 语言的最新tweets 并将它们打印出来。Groovy 能够使用Java 库twitter4j 很容易就编写出这样的程序,如清单1 所示: 清单1. 串行读取tweets (langTweets.groovy) import twitter4j.Twitter import twitter4j.Query

Tomcat线程池实现简介

Tomcat线程池实现简介 目前市场上常用的开源Java Web容器有Tomcat、Resin和Jetty。其中Resin从V3.0后需要购买才能用于商业目的,而其他两种则是纯开源的。可以分别从他们的网站上下载最新的二进制包和源代码。 作为Web容器,需要承受较高的访问量,能够同时响应不同用户的请求,能够在恶劣环境下保持较高的稳定性和健壮性。在HTTP服务器领域,Apache HTTPD的效率是最高的,也是最为稳定的,但它只能处理静态页面的请求,如果需要支持动态页面请求,则必须安装相应的插件,比如mod_perl可以处理Perl脚本,mod_python可以处理Python脚本。 上面介绍的三中Web容器,都是使用Java编写的HTTP服务器,当然他们都可以嵌到Apache 中使用,也可以独立使用。分析它们处理客户请求的方法有助于了解Java多线程和线程池的实现方法,为设计强大的多线程服务器打好基础。 Tomcat是使用最广的Java Web容器,功能强大,可扩展性强。最新版本的Tomcat(5.5.17)为了提高响应速度和效率,使用了Apache Portable Runtime(APR)作为最底层,使用了APR 中包含Socket、缓冲池等多种技术,性能也提高了。APR也是Apache HTTPD的最底层。可想而知,同属于ASF(Apache Software Foundation)中的成员,互补互用的情况还是很多的,虽然使用了不同的开发语言。 Tomcat 的线程池位于tomcat-util.jar文件中,包含了两种线程池方案。方案一:使用APR 的Pool技术,使用了JNI;方案二:使用Java实现的ThreadPool。这里介绍的是第二种。如果想了解APR的Pool技术,可以查看APR的源代码。 ThreadPool默认创建了5个线程,保存在一个200维的线程数组中,创建时就启动了这些线程,当然在没有请求时,它们都处理“等待”状态(其实就是一个while循环,不停的等待notify)。如果有请求时,空闲线程会被唤醒执行用户的请求。 具体的请求过程是:服务启动时,创建一个一维线程数组(maxThread=200个),并创建空闲线程(minSpareThreads=5个)随时等待用户请求。当有用户请求时,调用threadpool.runIt(ThreadPoolRunnable)方法,将一个需要执行的实例传给ThreadPool中。其中用户需要执行的实例必须实现ThreadPoolRunnable接口。ThreadPool 首先查找空闲的线程,如果有则用它运行要执行ThreadPoolRunnable;如果没有空闲线程并且没有超过maxThreads,就一次性创建minSpareThreads个空闲线程;如果已经超过了maxThreads了,就等待空闲线程了。总之,要找到空闲的线程,以便用它执行实例。找到后,将该线程从线程数组中移走。接着唤醒已经找到的空闲线程,用它运行执行实例(ThreadPoolRunnable)。运行完ThreadPoolRunnable后,就将该线程重新放到线程数组中,作为空闲线程供后续使用。 由此可以看出,Tomcat的线程池实现是比较简单的,ThreadPool.java也只有840行代码。用一个一维数组保存空闲的线程,每次以一个较小步伐(5个)创建空闲线程并放到线程池中。使用时从数组中移走空闲的线程,用完后,再“归还”给线程池。ThreadPool提供的仅仅是线程池的实现,而如何使用线程池也是有很大学问的。让我们看看Tomcat是如何使用ThreadPool的吧。 Tomcat有两种EndPoint,分别是AprEndpoint和PoolTcpEndpoint。前者自己实现了一套线

多线程编程的详细说明完整版

VB .NET多线程编程的详细说明 作者:陶刚整理:https://www.doczj.com/doc/d41876490.html, 更新时间:2011-4-1 介绍 传统的Visual Basic开发人员已经建立了同步应用程序,在这些程序中事务按顺序执行。尽管由于多个事务多多少少地同时运行使多线程应用程序效率更高,但是使用先前版本的Visual Basic很难建立这类程序。 多线程程序是可行的,因为操作系统是多任务的,它有模拟同一时刻运行多个应用程序的能力。尽管多数个人计算机只有一个处理器,但是现在的操作系统还是通过在多个执行代码片断之间划分处理器时间提供了多任务。线程可能是整个应用程序,但通常是应用程序可以单独运行的一个部分。操作系统根据线程的优先级和离最近运行的时间长短给每一个线程分配处理时间。多线程对于时间密集型事务(例如文件输入输出)应用程序的性能有很大的提高。 但是也有必须细心的地方。尽管多线程能提高性能,但是每个线程还是需要用附加的内存来建立和处理器时间来运行,建立太多的线程可能降低应用程序的性能。当设计多线程应用程序时,应该比较性能与开销。 多任务成为操作系统的一部分已经很久了。但是直到最近Visual Basic程序员才能使用无文档记录特性(undocumented)或者间接使用COM组件或者操作系统的异步部分执行多线程事务。.NET框架组件为开发多线程应用程序,在System.Threading名字空间中提供了全面的支持。 本文讨论多线程的好处以及怎样使用Visual Basic .NET开发多线程应用程序。尽管Visual Basic .NET和.NET框架组件使开发多线程应用程序更容易,但是本文作了调整使其适合高级读者和希望从早期Visual Basic转移到Visual Basic .NET的开发人员。 多线程处理的优点 尽管同步应用程序易于开发,但是它们的性能通常比多线程应用程序低,因为一个新的事务必须等待前面的事务完成后才能开始。如果完成某个同步事务的时间比预想的要长,应用程序可能没有响应。多线程处理可以同时运行多个过程。例如,字处理程序能够在继续操作文档的同时执行拼写检查事务。因为多线程应用程序把程序分解为独立的事务,它们能通过下面的途径充分提高性能: l 多线程技术可以使程序更容易响应,因为在其它工作继续时用户界面可以保持激活。 l 当前不忙的事务可以把处理器时间让给其它事务。 l 花费大量处理时间的事务可以周期性的把时间让给其它的事务。 l 事务可以在任何时候停止。 l 可以通过把单独事务的优先级调高或调低来优化性能。 明确地建立多线程应用程序的决定依赖于几个因素。多线程最适合下面的情况:

11线程池的使用

第11章线程池的使用 第8章讲述了如何使用让线程保持用户方式的机制来实现线程同步的方法。用户方式的同步机制的出色之处在于它的同步速度很快。如果关心线程的运行速度,那么应该了解一下用户方式的同步机制是否适用。 到目前为止,已经知道创建多线程应用程序是非常困难的。需要会面临两个大问题。一个是要对线程的创建和撤消进行管理,另一个是要对线程对资源的访问实施同步。为了对资源访问实施同步,Wi n d o w s提供了许多基本要素来帮助进行操作,如事件、信标、互斥对象和关键代码段等。这些基本要素的使用都非常方便。为了使操作变得更加方便,唯一的方法是让系统能够自动保护共享资源。不幸的是,在Wi n d o w s提供一种让人满意的保护方法之前,我们已经有了一种这样的方法。 在如何对线程的创建和撤消进行管理的问题上,人人都有自己的好主意。近年来,我自己创建了若干不同的线程池实现代码,每个实现代码都进行了很好的调整,以便适应特定环境的需要。M i c r o s o f t公司的Windows 2000提供了一些新的线程池函数,使得线程的创建、撤消和基本管理变得更加容易。这个新的通用线程池并不完全适合每一种环境,但是它常常可以适合你的需要,并且能够节省大量的程序开发时间。 新的线程池函数使你能够执行下列操作: ? 异步调用函数。 ? 按照规定的时间间隔调用函数。 ? 当单个内核对象变为已通知状态时调用函数。 ? 当异步I / O请求完成时调用函数。 为了完成这些操作,线程池由4个独立的部分组成。表11 - 1显示了这些组件并描述了控制其行为特性的规则。 表11-1 线程池的组件及其行为特性

Java定时任务ScheduledThreadPoolExecutor

Timer计时器有管理任务延迟执行("如1000ms后执行任务")以及周期性执行("如每500ms执行一次该任务")。但是,Timer存在一些缺陷,因此你应该考虑使用ScheduledThreadPoolExecutor作为代替品,Timer对调度的支持是基于绝对时间,而不是相对时间的,由此任务对系统时钟的改变是敏感的;ScheduledThreadExecutor只支持相对时间。 Timer的另一个问题在于,如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。Timer线程并不捕获异常,所以TimerTask抛出的未检查的异常会终止timer 线程。这种情况下,Timer也不会再重新恢复线程的执行了;它错误的认为整个Timer都被取消了。此时,已经被安排但尚未执行的TimerTask永远不会再执行了,新的任务也不能被调度了。 例子: packagecom.concurrent.basic; importjava.util.Timer; import java.util.TimerTask; public class TimerTest { private Timer timer = new Timer(); // 启动计时器 public void lanuchTimer() { timer.schedule(new TimerTask() { public void run() { throw new RuntimeException(); } }, 1000 * 3, 500); } // 向计时器添加一个任务 public void addOneTask() { timer.schedule(new TimerTask() { public void run() { System.out.println("hello world"); } }, 1000 * 1, 1000 * 5); }

线程池原理 C++实现

线程池原理及创建(C++实现) 时间:2010‐02‐25 14:40:43来源:网络 作者:未知 点击:2963次 本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关。另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后,我们给出一个 本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关。另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后,我们给出一个简单示例程序,通过该示例程序,我们会发现,通过该线程池框架执行多线程任务是多么的简单。 为什么需要线程池 目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。 传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。 我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。 T1:线程创建时间 T2:线程执行时间,包括线程的同步等时间 T3:线程销毁时间

那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%‐50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。 除此之外,线程池能够减少创建的线程个数。通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。 因此线程池的出现正是着眼于减少线程池本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。 基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销。 构建线程池框架 一般线程池都必须具备下面几个组成部分: 线程池管理器:用于创建并管理线程池 工作线程: 线程池中实际执行的线程

tcl多线程编程的使用说明

321C H A P T E R II. Advanced Tcl 21Multi-Threaded Tcl Scripts This chapter describes the Thread extension for creating multi-threaded Tcl scripts. This Chapter is from Practical Programming in Tcl and Tk, 4th Ed. Copyright 2003 ? Brent Welch, Ken Jones https://www.doczj.com/doc/d41876490.html,/book/ T hread support, a key feature of many languages, is a recent addition to Tcl. That’s because the Tcl event loop supports features implemented by threads in most other languages, such as graphical user interface management, multi-client servers, asynchronous communication,and scheduling and timing operations. However, although Tcl’s event loop can replace the need for threads in many circumstances, there are still some instances where threads can be a better solution: ?Long-running calculations or other processing, which can “starve” the event loop ?Interaction with external libraries or processes that don’t support asynchro- nous communication ?Parallel processing that doesn’t adapt well to an event-driven model ?Embedding Tcl into an existing multi-threaded application What are Threads? Traditionally , processes have been limited in that they can do only one thing at a time. If your application needed to perform multiple tasks in parallel, you designed the application to create multiple processes. However, this approach has its drawbacks. One is that processes are relatively “heavy” in terms of the resources they consume and the time it takes to create them. For applications that frequently create new processes — for example, servers that create a new

100行Java代码构建一个线程池

100行Java代码构建一个线程池 本示例程序由三个类构成,第一个是TestThreadPool类,它是一个测试程序,用来模拟客户端的请求,当你运行它时,系统首先会显示线程池的初始化信息,然后提示你从键盘上输入字符串,并按下回车键,这时你会发现屏幕上显示信息,告诉你某个线程正在处理你的请求,如果你快速地输入一行行字符串,那么你会发现线程池中不断有线程被唤醒,来处理你的请求,在本例中,我创建了一个拥有10个线程的线程池,如果线程池中没有可用线程了,系统会提示你相应的警告信息,但如果你稍等片刻,那你会发现屏幕上会陆陆续续提示有线程进入了睡眠状态,这时你又可以发送新的请求了。 第二个类是ThreadPoolManager类,顾名思义,它是一个用于管理线程池的类,它的主要职责是初始化线程池,并为客户端的请求分配不同的线程来进行处理,如果线程池满了,它会对你发出警告信息。 最后一个类是SimpleThread类,它是Thread类的一个子类,它才真正对客户端的请求进行处理,SimpleThread在示例程序初始化时都处于睡眠状态,但如果它接受到了ThreadPoolManager类发过来的调度信息,则会将自己唤醒,并对请求进行处理。 首先我们来看一下TestThreadPool类的源码: //TestThreadPool.java 1 import java.io.*; 2 3 4 public class TestThreadPool 5 { 6 public static void main(String[] args) 7 { 8 try{ 9 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 10 String s; 11 ThreadPoolManager manager = new ThreadPoolManager(10); 12 while((s = br.readLine()) != null) 13 {

TASK使用总结

Task类使用总结 2012-12-27 20:51:19 标签:C#多线程Task并行 原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本声明。否则将追究法律责任。 https://www.doczj.com/doc/d41876490.html,/2359144/1102476 由于Framework 4.0和Framework 4.5对Task类稍微有些不同,此处声明以下代码都是基于Framework 4.5 Task类和Task类,后者是前者的泛型版本。TResult类型为Task所调用方法的返回值。 主要区别在于Task构造函数接受的参数是Action委托,而Task接受的是Func委托。 1.Task(Action) 2.Task(Func) 启动一个任务 1.static void Main(string[] args) 2.{ 3.Task Task1 = new Task(() => Conso le.WriteLine("Task1")); 4.Task1.Start(); 5.Console.ReadLine(); 6.} 通过实例化一个Task对象,然后Start,这种方式中规中矩,但是实践中,通常采用更方便快捷的方式 Task.Run(() => Console.WriteLine("Foo")); 这种方式直接运行了Task,不像上面的方法还需要调用Start(); Task.Run方法是Task类中的静态方法,接受的参数是委托。返回值是为该Task 对象。 Task.Run(Action) Task.Run(Func>)

IOCP 原理 代码

Windows I/O完成端口 2009-10-30 10:51 WINDOWS完成端口编程 1、基本概念 2、WINDOWS完成端口的特点 3、完成端口(Completion Ports )相关数据结构和创建 4、完成端口线程的工作原理 5、Windows完成端口的实例代码 WINDOWS完成端口编程 摘要:开发网络程序从来都不是一件容易的事情,尽管只需要遵守很少的一些规则:创建socket,发起连接,接受连接,发送和接收数据,等等。真正的困难在于:让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windows完成端口进行重叠I/O的技术,可以很方便地在Windows平台上开发出支持大量连接的网络服务程序。本文介绍在Windows平台上使用完成端口模型开发的基本原理,同时给出实际的例子。本文主要关注C/S结构的服务器端程序,因为一般来说,开发一个大容量、具有可扩展性的winsock程序就是指服务程序。 1、基本概念 设备---指windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile 等,所以我们不能看到**File函数就只想到文件设备。 与设备通信有两种方式,同步方式和异步方式:同步方式下,当调用ReadFile这类函数时,函数会等待系统执行完所要求的工作,然后才返回;异步方式下,ReadFile这类函数会直接返回,系统自己去完成对设备的操作,然后以某种方式通知完成操作。 重叠I/O----顾名思义,就是当你调用了某个函数(比如ReadFile)就立刻返回接着做自己的其他动作的时候,系统同时也在对I/0设备进行你所请求的操作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。所以,重叠I/O是在异步方式下使用I/O设备的。重叠I/O需要使用的一个非常重要的数据结构:OVERLAPPED。 2、WINDOWS完成端口的特点 Win32重叠I/O(Overlapped I/O)机制允许发起一个操作,并在操作完成之后接收信息。对于那种需要很长时间才能完成的操作来说,重叠IO机制尤其有用,因为发起重叠操作的线程在重叠请求发出后就可以自由地做别的事情了。在WinNT和Win2000上,提供的真正可扩展的I/O模型就是使用完成端口(Completion Port)的重叠I/O。完成端口---是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非得使用完成端口不可,同样设备内核对象、事件对象、告警I/0等也可使用。但是完成端口内部提

相关主题
文本预览
相关文档 最新文档