励精图治---Concurrency---如何创建多线程

来源:互联网 发布:火花棱镜淘宝 编辑:程序博客网 时间:2024/05/01 03:22

多线程任务的创建

        我们将程序的工作分解到多个任务中。尽量让各个人物之间保持独立。


多线程任务的演化

1. 单线程

class SingleThreadWebServer {

    public static void main(String[] args) throws IOException {

        ServerSocket socket = new ServerSocket(80);

        while(true){

            Socket connection = socket.accept();

            handleRequest(connection);

        }

    }

}

特征:串行设计,在网络环境下会极大的影响资源利用率。

适用情况:handleRequest(connection)处理非常快,同时可以立即返回。

实际情况:网络环境下的服务器,有几个情况:io,网络延迟拥塞,数据库操作。都会影响吞吐率。串行设计在这种情况下不可用


2.添加多线程设计

class MultiThreadWebServer {

    public static void main(String[] args) {

        ServerSocket socket = new ServerSocket(80);

        while (true) {

            final Socket connection = socket.accept();

            Runnable task = new Runnable() {

                public void run() {

                    handleRequest(connection);

                }

            }

            new Thread(task).start();

        }

    }

}

特征:每收到一个请求就创建一个线程

适用情况:负荷低,服务器能承受。没什么量可以这么干。

实际情况:这样就会无限制创建线程。一旦超过负荷,对服务器的性能影响就非常大。

1.是线程生命周期的开销非常大。线程创建的时候,往往是轻量的,每个线程产生的时候需要消耗大量的CPU计算。

2.活跃线程会占用大量内存,线程间的CPU资源竞争也会带来巨大负荷。

3.JVM在创建线程上有限制,太多的线程会带来稳定性影响。可能会抛出outofmemoryexception

ps:java在运行代码时,有两个栈,一个是原始代码,一个是java代码,原始代码就是java被虚拟机解释出来的。

线程的话,也是两个栈。

JVM在默认情况下会产生一个复合的栈,大概是0.5M

在32位的机器上,2^32的内存除以每个栈的大小。就是最大可以容纳的线程数。是几千到几万之间。看机器配置了多少内存。现在一般都是4G。超过了2^32.


3.利用executor限制线程数量

class TaskExecutionWebServer {

    private static final int NTHREADS = 100;

    private static final Execution exec = Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args){

        ServerSocket socket = new SocketSocket(80);

        while(true){

            final Socket connection = socket.accept();

            Runnable task = new Runnable() {

                public void run(){

                    handleRequest(connection);

                }

            };

            exec.execute(task);

        }

    }

}

特征:即便在实施阶段也可以修改。修改代价低。execution是异步执行框架。限制了创建线程的数量。

适用情况:

实际情况:改善了多线程处理的负荷极限


4. 执行策略

1. 在什么线程中执行任务

2. 任务按照什么顺序执行

3. 有多少个任务能并发

4. 在任务中有多少个任务等待执行

5. 如果过载,如果选择任务,如何通知任何任何应用程序有任务被拒绝?

6. 在执行一个任务之前或之后,应该进行哪些动作?



5. 优化线程分配策略:线程池

从线程池中分配线程,可以提高响应性,提高了资源利用率,防止多线程间的资源竞争。


jdk现成代码

newFixedThreadPool: 创建一个固定长度的线程池,没提交一个任务就创建一个线程,直到达到线程池的最大数量。如果某个线程发生了exception,那这个线程就不存在了,会重新补充一个线程。

newCachedThreadPool: 创建不固定长度的线程池。当前线程规模大于处理规模时,那么将回收空闲线程,当需求增加时,线程池的规模不存在任何限制。

newSingleThreadExecutor: 单线程的executor. 按照某个顺利来串行执行(FIFO,LIFO,priority)

newScheduledThreadPool: 创建固定长度的线程池。可以延迟或定时方式来执行任务。


6. Executor的生命周期

1. 安全的关闭形式:完成所有已经启动的任务,并且不再接受任何新任务

2. 不安全的方式:直接掉电。将在关闭操作中受影响的任务的状态反馈给应用程序。

运行状态:运行,关闭,终止

public interface ExecutorService extends Executor {

    void shutdown();

    List<Runnable> shutdownNow();

    boolean isShutdown();

    boolean isTerminated();

    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

}


class LifecycleWebServer {

    private final ExecutorService exec = ...;

    public void start() throws IOException {

        ServerSocket socket = new ServerSocket(80);

        while (!exec.isShutdown()) {

            try {

                final Socket conn  = socket.accept();

                exec.execute(new Runnable() {

                public void run() { handleRequest(conn); }

            });

        }catch(RejectedExecutionException e) {

            if(!exec.isShutdown()) log("task submission rejected", e);

        }

    }

}


public void stop () { exec.shutdown(); }


void handleRequest(Socket connection) {

    Request req = readRequest(connection);

    if(isShutdownRequest(req))

        stop();

    else

        dispatchRequest(req);

    }

}


7. 延迟任务与周期任务

使用环境:Timer在执行所有定时任务时只会创建一个线程。那么在某任务时间过长,会影响其他任务,破坏其他TimerTask的定时准确性。

会出现原来本来40ms执行周期的,可能会变成连续执行。

DelayQueue: 某个任务逾期后,其他的任务都会按照顺序根据延迟时间进行排序。



4.优化线程分配策略:线程池

从线程池中分配线程,可以提高响应性,提高了资源利用率,防止多线程间的资源竞争。


范例:
HTML文档进行串行处理
public class SingleThreadRenderer {
    void renderPage(CharSequence source) {
        renderText(source);
        List<ImageData> imageData = new ArrayList<ImageData>();
        for (ImageInfo imageInfo : scanForImageInfo(source))
            imageData.add(imageInfo.downloadImage());
        for (ImageData data : imageData)
            renderImage(data);
    }
}
特点: 先renderText,再获取image信息并下载,最后循环renderImage。
缺点:图像下载过程的大部分时间都在等待IO操作执行完成,资源利用率很低。


优化:利用executor进行多线程操作
public class FutureRenderer {
    private final ExecutorService executor = ...;
    void renderPage(CharSequence source) {
        final List<ImageInfo> imageInfos = scanForImageInfo(source);
        Callable<List<ImageData>> task = 
                new Callable<List<ImageData>>() {
                    List<ImageData> call() {
                        List<ImageData> result
                                = new ArrayList<ImageData>();
                        for (ImageInfo imageInfo :imageInfos) 
                            result.add(imageInfo.downloadImage());
                        return result;
                    }
                };
        Future<List<ImageData>> future = executor.submit(task);
        renderText(source);
        try {
            List<ImageData> imageData = future.get();
            for (ImageData data : imageData)
                renderImage(data);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            future.cancel(true);
        } catch (ExecutionException e) {
            throw launderThrowable(e.getCause());
        }
    }
}
特点:先获取image信息,利用future和callable创建一个task,提交task双线程下载,同时,先创建text,future.get会阻塞等待完成,然后renderImage。
减少了阻塞等待的时间。
缺点:renderText与renderImage的线程存在消耗时间的差异,可能一个很快一个很慢,这样实际优化的时间不多。
只有当大量相互独立且同构的任务可以并发进行处理时,才能体现出程序的工作负载分配到多个任务中带来的真正的性能提升。




优化:利用线程池增加并发线程数量
public class Renderer {
    private final ExecutorService executor;
    
    Renderer(ExecutorService executor) { this.executor = executor;}
    
    void renderPage(CharSequence source) {
        List<ImageInfo> infos = scanForImageInfo(source);
        CompletionService<ImageData> completionService = 
            new ExecutorCompletionService<ImageData>(executor);
        for (final ImageInfo imageInfo : infos)
            completionService.submit(new Callable<ImageData>() {
                public ImageData call() {
                    return imageInfo.downloadImage();
                }
            });
        renderText(source);
        try {
            for (int t = 0, n = info.size(); t < n; t++) {
                Future<ImageData> f = completionService.take();
                ImageData imageData = f.get();
                renderImage(imageData);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (ExecutionException e) {
                throw launderThrowable(e.getCause());
            }
        }
    }
}
特点:先扫描image信息,利用ExecutorService这个线程池,循环提交下载图片的任务(提前开始download image), 然后renderText,再用阻塞的方式一张一张的renderImage。
这里为每个图像下载创建了一个独立线程,并在线程池中执行,每张图片在下载完成后就立即显示出来。(renderText时间短,跟renderImage在不同线程中)
ExecutorCompletionService:Executor和BlockingQueue的功能融合在一起,实现上会将callable任务提交执行,然后用类似队列操作的take和poll等方法来获得已完成的结果,
这些结果会在任务完成的时候封装成Future。就可以用阻塞方式一个一个get。
缺点:获取时间不确定,没有办法在失败后显示一个错误的图片。不能在任务失败后提供一个出错后的显示。


优化:利用Future.get增加超时设置
Page renderPageWithAd() throws InterruptedException {
    long endNanos = Systen.nanoTime() + TIME_BUGGET;
    Future<Ad> f = exec.submit(new FetchAdTask());
    Page page = renderPageBody();
    Ad ad;
    try {
        long timeLeft = endNanos - System.nanoTime();
        ad = f.get(timeLeft, NANOSECONDS);
    } catch (ExecutionExection e) {
        ad = DEFAULT_AD;
    } catch (TimeoutException e) {
        ad = DEFAULT_AD;
        f.cancel(true);
    }
    page.setAd(ad);
    return page;
}
特点:在之前的基础上,增加了超时功能。超时后,用默认值代替。


范例:在预定时间内请求信息
private class QuoteTask implements Callable<TravelQuote> {
    private final TravelCompany company;
    private final TravelInfo travelInfo;
    ...
    public TravelQuote call() throws Exception {
        return company.solicitQuote(travelInfo);
    }
}


public List<TravelQuote> getRankedTravelQuotes (
        TravelInfo travelInfo, Set<TravelCompany> companies, 
        Comparator<travelQuote> ranking, long time, TimeUnit unit) throws InterruptedException {
    List<QuoteTask> tasks = new ArrayList<QuoteTask>();
    for (TravelCompany company : companies) 
        tasks.add(new QuoteTask(company, travelInfo));
    
    List<Future<TravelQuote>> futures = 
        exec.invokeAll(tasks, time, unit);//#这里提交任务
    
    List<TravelQuotes> quotes = 
        new ArrayList<TravelQuote>(tasks.size());
    Iterator<QuoteTask> taskIter = tasks.iterator();
    for (Future<TravelQUote> f :futures) {
        QuoteTask task = taskIter.next();
        try {
            quotes.add(f.get());
        } catch (ExecutionException e) {
            quotes.add(task.getFailureQuote(e.getCause()));
        } catch (CancellationException e) {
            quotes.add(task.getTimeoutQuote(e));
        }
    }
    
    Collections.sort(quotes, ranking);
    return quotes;
}
特点:任务间无关。任务超时后,所有任务的结果,都会被捕获。

0 0
原创粉丝点击