你所不知道的五件事情--java.util.concurrent(第二部分)

这是Ted NewardIBM developerWorks5 things系列文章中的一篇,仍然讲述了关于Java并发集合API的一些应用窍门,值得大家学习。(2010.06.17最后更新)

摘要:除了便于编写并发应用的集合API外,java.util.concurrent还引入了其它的预置程序组件,这些组件能辅助你在多线程应用中控制和执行线程。Ted Neward再介绍了五个来自于java.util.concurrent的Java编程必备窍门。

通过提供线程安全,性能良好的数据结构,并发集合框架使并发编程变得更容易。然而在有些情况下,开发者需要多走一步,并要考虑控制和/或调节线程的执行。提供java.util.concurrent包的全部原因就是为了简化多线程编程--事实上正是如此。
    接着第一部分,本文介绍了多个同步数据结构,这些数据结构比核心语言基本结构(监视器)的层次要高,但不会高到将它们圈囿在一个集合类中。一旦知道了这些锁与栓的用途,就能径直去用了。

1. 信号量

    在有些企业级系统中,常需要开发者去控制针对特定资源的请求(线程或动作)的数量。虽然完全可能试着手工编写这样的调节程序,但使用Semaphore类会更容易些,该类会为你处理对线程的控制,如清单1所示:

清单1. 使用信号量调节线程

import java.util.*;import java.util.concurrent.*;

public class SemApp
{
    public static void main(String[] args)
    {
        Runnable limitedCall = new Runnable() {
            final Random rand = new Random();
            final Semaphore available = new Semaphore(3);
            int count = 0;
            public void run()
            {
                int time = rand.nextInt(15);
                int num = count++;

                try
                {
                    available.acquire();

                    System.out.println("Executing " +
                        "long-running action for " +
                        time + " seconds #" + num);

                    Thread.sleep(time * 1000);

                    System.out.println("Done with #" +
                        num + "!");

                    available.release();
                }
                catch (InterruptedException intEx)
                {
                    intEx.printStackTrace();
                }
            }
        };

        for (int i=0; i<10; i++)
            new Thread(limitedCall).start();
    }
}

虽然上例有10个线程在运行(针对运行SemApp的Java进程执行jstack程序可以验证这一点),但只有3个是活动的。另外7个线程会被保存起来,直到其中一个信号量计数器被释放出来。(准确地说,Semaphore类支持一次获取和释放一个以上的被许可线程,但在此处的场景中这么做没有意义。)

2. CountDownLatch
    如果并发类Semaphore是被设计为在同一时刻允许"其中"一个线程执行的话,那么CountDownLatch就是赛马比赛中的起跑门。该类持有所有的线程,当遇到某个特定条件,那时CountDownLatch就会一次性释放全部的线程。

清单2. CountDownLatch:让我们比赛!

import java.util.*;
import java.util.concurrent.*;

class Race
{
    private Random rand = new Random();

    private int distance = rand.nextInt(250);
    private CountDownLatch start;
    private CountDownLatch finish;

    private List<String> horses = new ArrayList<String>();

    public Race(String names)
    {
        this.horses.addAll(Arrays.asList(names));
    }

    public void run()
        throws InterruptedException
    {
        System.out.println("And the horses are stepping up to the gate");
        final CountDownLatch start = new CountDownLatch(1);
        final CountDownLatch finish = new CountDownLatch(horses.size());
        final List<String> places =
            Collections.synchronizedList(new ArrayList<String>());

        for (final String h : horses)
        {
            new Thread(new Runnable() {
                public void run() {
                    try
                    {
                        System.out.println(h +
                            " stepping up to the gate");
                        start.await();

                        int traveled = 0;
                        while (traveled < distance)
                        {
                            // In a 0-2 second period of time.
                            Thread.sleep(rand.nextInt(3) * 1000);

                            //  a horse travels 0-14 lengths
                            traveled += rand.nextInt(15);
                            System.out.println(h +
                                " advanced to " + traveled + "!");
                        }
                        finish.countDown();
                        System.out.println(h +
                            " crossed the finish!");
                        places.add(h);
                    }
                    catch (InterruptedException intEx)
                    {
                        System.out.println("ABORTING RACE!!!");
                        intEx.printStackTrace();
                    }
                }
            }).start();
        }

        System.out.println("And they‘re off!");
        start.countDown();        

        finish.await();
        System.out.println("And we have our winners!");
        System.out.println(places.get(0) + " took the gold");
        System.out.println(places.get(1) + " got the silver");
        System.out.println("and " + places.get(2) + " took home the bronze.");
    }
}

public class CDLApp
{
    public static void main(String[] args)
        throws InterruptedException, java.io.IOException
    {
        System.out.println("Prepping");

        Race r = new Race(
            "Beverly Takes a Bath",
            "RockerHorse",
            "Phineas",
            "Ferb",
            "Tin Cup",
            "I‘m Faster Than a Monkey",
            "Glue Factory Reject"
            );

        System.out.println("It‘s a race of " + r.getDistance() + " lengths");

        System.out.println("Press Enter to run the race.");
        System.in.read();

        r.run();
    }
}

注意在清单2中,CountDownLatch服务于两个目的:首先,它同时释放所有的线程,模拟比赛的开始;但之后,另一个CountDownLatch模拟了比赛的结束。一场比赛会有更多的评论,你可以在比赛的"转弯"和"半程"点添加CountDownLatch,当马匹跑过1/4程,半程和3/4程时。

3. Executor
    清单1和清单2中的例子都遭遇了一个令人非常沮丧的错误,你被迫要直接地创建Thread对象。这是一个造成麻烦的方式,因为在有些JVM中,创建Thread对象是一件重量级的工作,所以重用而非创建新的线程要好得多。然而在另一些JVM中,情况就恰恰相反:Thread是非常轻量级的,若你需要一个线程,直接创建它则会好得多。当然,如果Murphy有他自己的方法(他经常就是这么做的),无论你使用哪种方法,对于你最终所依赖的某种Java平台都会是错误的。
    JSR-166专家组在一定程度上预见到了这种情况。与让Java开发者直接创建Thread实例不同,他们推荐Executor接口,这是一个创建新线程的抽象。如果清单3所示,Executor允许你自己不必使用new操作符去创建Thread对象:

清单3. Executor

Executor exec = getAnExecutorFromSomeplace();
exec.execute(new Runnable() {  });

使用Excutor的主要缺点与我们使用所有对象工厂所遇到的缺点一样:工厂必须来源于某处。不幸地是,不同于CLR,JVM并不带有一个标准的VM范围内的线程池。
    Executor类只是作为获取Executor实现实例的常用地方,但它只有new方法(例如,为了创建新的线程池);它没有预创建的实例。所以,如果你想创建并使用一个能贯穿于整个程序的Executor实现,你就可以创建一个你自己的Executor实例。(或者,在有些情况下,你可以使用你所选容器/平台所提供的Executor实例。)

ExecutorService,为你服务

    ExecutorService的用处在于使你不必关心Thread来自于何处,Executor接口缺乏Java开发者可能期望的一些功能,比如启动一个线程,该线程用于产生结果,它会以非阻塞方式一直等待,直到结果出现为止。(在桌面应用中这是很普通的需求,在这种应用中用户会执行一个需要访问数据库的UI操作,如果它耗时太长的话,就可能想要在它完成之前就取消这一操作。)
    为此,JSR-166的专家们创造一个更为有用的抽象,ExecutorService接口,该接口将启动线程的工厂模型化为一个服务,这样就能对该服务进行集合化控制了。例如,不对每个任务调用一次execute()方法,ExecutorService能创建一个任务的集合,并可返回代表这些任务未来结果的Future集合。

4. ScheduledExecutorServices
    与ExecutorService接口同样优秀,特定的任务需要以计划的形式进行执行,例如在特定的时间间隔或在特定的时刻执行给定的任务。这就是继承自ExecutorService的ScheduledExecutorService的职责范畴。
    如果你的目的是创建一个"心跳"命令,该命令每5秒钟就去"ping"一次。ScheduledExecutorService会帮你做到这一点,正如你在清单4中所见的那般简单:

清单4. ScheduledExecutorService按计划去"Ping"

import java.util.concurrent.*;

public class Ping
{
    public static void main(String[] args)
    {
        ScheduledExecutorService ses =
            Executors.newScheduledThreadPool(1);
        Runnable pinger = new Runnable() {
            public void run() {
                System.out.println("PING!");
            }
        };
        ses.scheduleAtFixedRate(pinger, 5, 5, TimeUnit.SECONDS);
    }
}

怎么样?没有操作线程的烦恼,如果用户想取消心跳,也不必操心如何去做,前台或后台都没有显示的标记线程;所有的调度细节都留给了ScheduledExecutorService。
    顺便提一下,如果用户想要取消心跳,从scheduleAtFixedRate()方法返回的会是一个ScheduledFuture实例,它不仅含有执行结果(如果有的话),也有一个cancel()方法去停止该计划任务。

5. 超时方法
    拥有为阻塞操作置一个确定的超时控制的能力(这样就可以避免死锁)是java.util.concurrent类库相比于旧有并发API,如针对锁的监视器,的最大优点之一。
    这些方法几乎总是按int/TimeUnit对的方式进行重载,该int/TimeUnit对用于指示方法在跳出执行并将控制返回给平台之前需要等待多长时间。这要求开发者对此做更多的工作--如果没有获得锁,将如何进行恢复?--但结果却几乎总是正确的:更少的死锁,以及更加生产安全的代码。(更多关于生产就绪的代码,请见Michael Nygard的Release It!)

结论
    java.util.concurrent包含有许多更优雅的工具,它们出于集合框架,但更胜之,特别是.locks和.atomic包中的类。深入挖掘之,你将发现像CyclicBarrier这样的十分有用的控制结构,甚至于更多。
    下一次,我们将步入一个新的主题:你所不知道的五件关于Jar的事情。

时间: 2024-08-24 16:27:55

你所不知道的五件事情--java.util.concurrent(第二部分)的相关文章

你所不知道的五件事情--java.util.concurrent(第一部分)

                                                            这是Ted Neward在IBM developerWorks中5 things系列文章中的一篇,讲述了关于Java并发集合API的一些应用窍门,值得大家学习.(2010.05.24最后更新) 摘要:编写既要性能良好又要防止应用崩溃的多线程代码确实很难--这也正是我们需要java.util.concurrent的原因.Ted Neward向你展示了像CopyOnWriteArr

聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore

前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票据,仅仅有拿到了票据的线程尽能够进入临界区,否则就等待.直到获得释放出的票据. Semaphore经常使用在资源池中来管理资源.当状态仅仅有1个0两个值时,它退化成了一个相互排斥的同步器.类似锁. 以下来看看Semaphore的代码. 它维护了一个内部类Sync来继承AQS,定制tryXXX方法来使

关于 java.util.concurrent 您不知道的 5 件事--转

第 1 部分 http://www.ibm.com/developerworks/cn/java/j-5things4.html Concurrent Collections 是 Java™ 5 的巨大附加产品,但是在关于注释和泛型的争执中很多 Java 开发人员忽视了它们.此外(或者更老实地说),许多开发人员避免使用这个数据包,因为他们认为它一定很复杂,就像它所要解决的问题一样. 事实上,java.util.concurrent 包含许多类,能够有效解决普通的并发问题,无需复杂工序.阅读本文,

(转)关于 Java 对象序列化您不知道的 5 件事

关于 Java 对象序列化您不知道的 5 件事 转自:http://developer.51cto.com/art/201506/479979.htm 数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数程序员对于 Java 平台都是浅尝则止,只学习了足以完成手头上任务的知识而已.在本 系列 中,Ted Neward 深入挖掘 Java 平台的核心功

关于Java你可能不知道的10件事

关于Java你可能不知道的10件事 分享到: 24 本文由 ImportNew - Jerry Lee 翻译自 Jooq.欢迎加入翻译小组.转载请参见文章末尾的要求. 呃,你是不是写Java已经有些年头了?还依稀记得这些吧: 那些年,它还叫做Oak:那些年,OO还是个热门话题:那些年,C++同学们觉得Java是没有出路的:那些年,Applet还风头正劲-- 但我打赌下面的这些事中至少有一半你还不知道.这周我们来聊聊这些会让你有些惊讶的Java内部的那些事儿吧. 1. 其实没有受检异常(check

关于 Java 性能监控您不知道的 5 件事,第 1 部分

责怪糟糕的代码(或不良代码对象)并不能帮助您发现瓶颈,提高 Java? 应用程序速度,猜测也不能帮您解决.Ted Neward 引导您关注 Java 性能监控工具,从5 个技巧开始,使用Java 5 的内置分析器JConsole 收集和分析性能数据. 当应用程序性能受到损害时,大多数开发人员都惊慌失措,这在情理之中.跟踪 Java 应用程序瓶颈来源一直以来都是很麻烦的,因为 Java 虚拟机有黑盒效应,而且 Java 平台分析工具一贯就有缺陷. 然而,随着 Java 5 中 JConsole 的

你所不知道的html5与html中的那些事(五)——web图像

文章简介: 现在的页面,一般都离不开图像,而怎么做才能让我们的页面中的图像加载的又快又好呢?在优化页面速度的时候还有什么事是你所不知道的呢?     下面看看今天我为大家带来了哪些关于web图像的你所平时不一定关心的事与一些有建设性的建议吧: 1)关于web页面中的图像你需要关注的关键点有那些? 2)web页面中图像的格式选择需要注意什么? 3)<img>标签的用法细节小结?   第一个问题 关于web页面中的图像你需要关注的关键点有那些?  示例图 像示例图中的图片一样,平时我们写页面都会用

关于Java Collections API您不知道的5件事,第2部分

注意可变对象 java.util 中的 Collections 类旨在通过取代数组提高 Java 性能.如您在 第 1 部分 中了解到的,它们也是多变的,能够以各种方 式定制和扩展,帮助实现优质.简洁的代码. Collections 非常强大,但是很多变:使用它们要小心,滥用它们会带来风险. 1. List 不同于数组 Java 开发人员常常错误地认为 ArrayList 就是 Java 数组的替代品.Collections 由数组支持,在集合内随机查找内容时性能较好. 与数组一样,集合使用整序

java中你所不知道的CAS操作

1.CAS是什么 Compare and Swap(比较并操作),由处理器架构支持,语义是如果当前值V和旧值A相同,则将当前值修改为B,如果不相同则不修改.CAS操作采用的是乐观锁技术,当多线程同时修改某个变量时只有一个成功,其他线程会失败当是不会被挂起,会被告知失败并重试.2.CAS操作和synchronized有什么区别呢 synchronized关键字采用悲观锁技术,线程独享锁,其他线程会被挂起知道锁被释放线程恢复,挂起和恢复会有很大的开销.3.java中CAS操作有哪些 java1.5之