java.util.concurrent,第2部分

并发集合通过提供线程安全的,经过良好调整的数据结构,使并发编程更加容易。 但是,在某些情况下,开发人员需要更进一步,考虑调整和/或限制线程执行。 鉴于java.util.concurrent的全部要点是简化多线程编程,您可能希望该程序包包含同步实用程序—确实如此。

本文是第1部分的后续文章,介绍了几种同步结构,这些结构比核心语言原语(监视器)的级别更高,但程度不高,以至于它们被埋藏在Collection类中。 一旦知道它们的用途,使用这些锁和大门非常简单。

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进行验证),但只有三个SemApp处于活动状态。 其他七个被搁置,直到其中一个信号量计数被释放。 (实际上, Semaphore类支持一次获取和释放多个许可证 ,但这在这种情况下是没有意义的。)

2. CountDownLatch

如果Semaphore是旨在允许线程一次“进入”的并发类(也许唤起了流行夜总会里蹦蹦跳跳的记忆),则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 es,因为马越过了距离的四分之一,一半和四分之三。

3.执行者

清单1清单2中的示例都具有相当令人沮丧的缺陷,因为它们迫使您直接创建Thread对象。 这是麻烦的秘诀,因为在某些JVM中,创建Thread是一项重量级的操作,与重用现有Thread相比创建新Thread要好得多。 但是,在其他JVM中,情况恰恰相反: Thread非常轻巧,最好在需要时使用new Thread 。 当然,如果墨菲按照他的方式行事(他通常会这样做),那么对于您最终在其上进行部署的平台而言,使用的任何一种方法都是完全错误的。

JSR-166专家组(请参阅参考资料 )在某种程度上预见了这种情况。 他们没有让Java开发人员直接创建Thread ,而是引入了Executor接口,该接口是用于创建新线程的抽象。 如清单3所示, Executor允许您创建线程,而不必自己new Thread对象:

清单3.执行器
Executor exec = getAnExecutorFromSomeplace();
exec.execute(new Runnable() { ... });

使用Executor的主要缺点与我们在所有工厂遇到的缺点相同:工厂必须来自某个地方。 不幸的是,与CLR不同,JVM没有附带标准的VM级线程池。

Executor类确实是获得Executor实例的常用场所,但是它只有new方法(例如,创建新的线程池)。 它没有预先创建的实例。 因此,如果您想在整个代码中创建和使用Executor实例,则需要您一个人做。 (或者,在某些情况下,您将能够使用所选容器/平台提供的实例。)

ExecutorService,随时为您服务

Executor接口虽然不必担心Thread的来源而有用,但它缺少Java开发人员可能期望的某些功能,例如能够启动旨在产生结果的线程并在非线程中等待的功能。阻止时尚,直到结果可用。 (这是台式机应用程序中的常见需求,在该应用程序中,用户将执行需要访问数据库的UI操作,但如果操作时间过长,则可能希望在操作完成之前取消该操作。)

为此,JSR-166专家创建了一个更为有用的抽象,即ExecutorService接口,该接口将线程启动工厂建模为可以集体控制的服务。 例如,与为每个任务调用一次execute()相比, ExecutorService可以采用任务集合并返回表示每个任务的未来结果的期货列表 。

4. ScheduledExecutorServices

ExecutorService接口一样,某些任务需要以计划的方式完成,例如以确定的间隔或在特定的时间执行给定的任务。 这是ScheduledExecutorService的省,该省扩展了ExecutorService

如果您的目标是创建一个每五秒钟“ 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库相对于其较早的并发表亲(例如,用于监视的监视器)的一大进步。

这些方法几乎总是以int / TimeUnit对重载,表明对方法进行搁置并将控制权返回给程序之前应等待多长时间。 开发人员需要做更多的工作-如果不获得锁,您将如何恢复? -但是结果几乎总是更正确:更少的死锁和更多的安全生产代码。 (欲了解更多有关编写产品代码,看到迈克尔·尼加德的发布吧!在相关主题 。)

结论

java.util.concurrent软件包包含许多漂亮的实用程序,它们远远超出了Collections的范围,尤其是在.locks.atomic软件包中。 深入研究,您还将发现有用的控件结构,例如CyclicBarrier等。

像Java平台的许多方面一样,您无需费劲查找可以非常有用的基础结构代码。 每当您编写多线程代码时,请记住本文和上一篇文章中讨论的实用程序。


翻译自: https://www.ibm.com/developerworks/java/library/j-5things5/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值