Sie sind auf Seite 1von 13

Cinco coisas que voc no sabia sobre... java.util.

concurrent, Parte 1
Programao multiencadeada com colees simultneas Ted Neward, Principal, Neward & Associates Resumo: A composio de cdigos multiencadeados que executam corretamente e protegem aplicativos contra danos muito difcil por isso que existe o java.util.concurrent. Ted Neward mostra como as classes de colees simultneas como CopyOnWriteArrayList, BlockingQueuee ConcurrentMap aperfeioam as classes de colees padro para suas necessidades de programao simultnea. Data: 17/Mai/2011 Nvel: Introdutrio Tambm disponvel em : Ingls Atividade: 2127 visualizaes Comentrios: 0 (Visualizar | Incluir comentrio - Conectar) Mdia de classificao (2 votos) Classificar este artigo

Sobre esta srie


Ento, voc acha que possui conhecimento sobre programao Java? A verdade que a maioria dos desenvolvedores tem apenas algum conhecimento sobre a plataforma Java, aprendendo o suficiente para concluir a tarefa. Nesta srie, Ted Neward examina a fundo a funcionalidade da plataforma Java para revelar fatos pouco conhecidos que podem lhe ajudar a superar at mesmo os desafios de programao mais difceis. As colees simultneas foram uma grande incluso no Java 5, mas muitos desenvolvedores Java ignoraram essa incluso devido grande propaganda sobre anotaes e genricos. Alm disso, e para falar a verdade, muitos desenvolvedores evitam esse pacote porque acreditam que ele, assim como os problemas que ele pretende resolver, complicado. Na verdade, o java.util.concurrent contm muitas classes que resolvem de forma efetiva problemas de simultaneidade comuns, sem a necessidade de um trabalho rduo. Leia sobre como as classes java.util.concurrent como CopyOnWriteArrayList e BlockingQueue ajudam a resolver os desafios prejudiciais de programao multiencadeada. 1. TimeUnit Apesar de no ser uma classe de colees por assim dizer, a enumerao java.util.concurrent.TimeUnit facilita muito a leitura do cdigo. O uso do

TimeUnit

livra os desenvolvedores que usam mtodo ou API do controle do milissegundo.


O TimeUnit incorpora todas as unidades de tempo, desde de MILISSEGUNDOS e MICROSSEGUNDOS at DIAS e HORAS, o que significa que ele manipula praticamente todos

os tipos de intervalos de tempo necessrios para o desenvolvedor. E, graas aos mtodos de converso declarados na enumerao, ainda mais simples converter HORAS em MILISSEGUNDOS quando o tempo acelera.

Voltar para parte superior 2. CopyOnWriteArrayList A execuo de uma nova cpia de um array uma operao muito cara, em termos de tempo e sobrecarga de memria, para ser usada normalmente, muitas vezes os desenvolvedores preferem usar um ArrayList sincronizado. No entanto, essa tambm uma opo cara, pois todas as vezes que repetida nos contedos de uma coleo necessrio sincronizar todas as operaes, incluindo leitura e gravao, para garantir a consistncia. Isso retrocede a estrutura de custo em cenrios nos quais diversos leitores leem o ArrayList , mas poucos o modificam.
CopyOnWriteArrayList CopyOnWriteArrayList

o que resolve esse problema. Seu Javadoc define como uma "variante thread-safe do ArrayList na qual todas as operaes variveis (add, set entre outras) so implementadas atravs da criao de uma nova cpia do array". A coleo copia internamente o seu contedo para um novo array em caso de modificao, dessa forma os leitores que acessam os contedos do array no ficam sujeitos aos custos de sincronizao (pois no esto operando em dados variveis). Basicamente, o CopyOnWriteArrayList ideal para o cenrio no qual o ArrayList falha: leitura frequente e gravao rara, como os ouvintesde um evento JavaBean.

Voltar para parte superior 3. BlockingQueue A interface BlockingQueue declara ser uma Fila, o que significa que seus itens so armazenados na ordem first in, first out (FIFO). Os itens inseridos em uma ordem especfica so recuperados na mesma ordem mas com a incluso da garantia de que qualquer tentativa de recuperao de um item de uma fila vazia ir bloquear o encadeamento de chamada at que o item esteja pronto para ser recuperado. Da mesma forma, qualquer tentativa de insero de um item em uma fila cheia ir bloquear o encadeamento de chamada at que haja espao disponvel no armazenamento da fila.

O BlockingQueue

resolve perfeitamente o problema de como "transferir" itens reunidos por um encadeamento para outro encadeamento para processamento, sem a preocupao com problemas de sincronizao. A trilha de blocos protegidos no tutorial do Java um bom exemplo. Ela desenvolve um buffer delimitado com um nico intervalo usando sincronizao manual e wait()/notifyAll() para sinalizar entre os encadeamentos quando um novo item est disponvel para consumo e quando o intervalo est pronto para ser preenchido com um novo item. (Consulte a seo de implementao de blocos protegidos para obter mais detalhes.) Apesar do cdigo no tutorial de blocos protegidos funcionar, ele longo, complicado e no totalmente intuitivo. No comeo da plataforma Java, os desenvolvedores realmente tinham que lidar com tais cdigos, mas em 2010 as coisas certamente melhoraram, no? A Listagem 1 mostra uma verso reescrita do cdigo dos blocos protegidos na qual foi empregado o ArrayBlockingQueue ao invs da verso manual Drop.

Listagem 1. BlockingQueue
import java.util.*; import java.util.concurrent.*; class Producer implements Runnable { private BlockingQueue<String> drop; List<String> messages = Arrays.asList( "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "Wouldn't you eat ivy too?"); public Producer(BlockingQueue<String> d) { this.drop = d; } public void run() { try { for (String s : messages) drop.put(s); drop.put("DONE"); } catch (InterruptedException intEx) { System.out.println("Interrupted! " + "Last one out, turn out the lights!"); } } } class Consumer implements Runnable { private BlockingQueue<String> drop; public Consumer(BlockingQueue<String> d) { this.drop = d; } public void run()

{ try { String msg = null; while (!((msg = drop.take()).equals("DONE"))) System.out.println(msg); } catch (InterruptedException intEx) { System.out.println("Interrupted! " + "Last one out, turn out the lights!"); } } } public class ABQApp { public static void main(String[] args) { BlockingQueue<String> drop = new ArrayBlockingQueue(1, true); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); } }

O ArrayBlockingQueue tambm cumpre a "integridade" o que significa que ele pode conceder aos encadeamentos de leitura e gravao o acesso primeiro a entrar, primeiro a sair. A alternativa seria uma poltica mais eficiente que poderia deixar alguns encadeamentos sem processamento. (Ou seja, seria mais eficiente permitir a execuo de leitores enquanto outros leitores permanecem bloqueados, mas isso poderia gerar um fluxo constante de encadeamentos de leitores o que impediria os gravadores de executarem sua tarefa.)

Vigilncia de erro!
A propsito, voc estava certo se observou que os blocos protegidos contm um grande erro o que aconteceria se um desenvolvedor sincronizasse a instncia Drop dentro de main()?
O BlockingQueue

tambm oferece suporte para mtodos que usam parmetros de tempo, indicando quanto tempo o segmento deve permanecer bloqueado antes de retornar a falha de sinal para inserir ou recuperar o item em questo. Isso evita uma espera ilimitada, o que poderia ser fatal para um sistema de produo, visto que uma espera ilimitada pode se tornar facilmente uma queda do sistema que requer a reinicializao.

Voltar para parte superior 4. ConcurrentMap

O map

possui um erro de simultaneidade sutil que j prejudicou muitos desenvolvedores Java desatentos. ConcurrentMap a soluo simples. Quando um Map acessado de diversos encadeamentos, comum usar containsKey() ou get() para descobrir se uma determinada chave est presente antes de armazenar o par chave/valor. Mas mesmo com um Mapsincronizado, um encadeamento poderia penetrar durante esse processo e confiscar o controle do Map. O problema que o bloqueio obtido no comeo de get()e liberado antes do bloqueio poder ser adquirido novamente, no chamado para put(). O resultado uma disputa entre dois encadeamentos e o resultado ser diferente dependendo do encadeamento que for executado primeiro. Se dois encadeamentos chamarem um mtodo ao mesmo tempo, ambos iro testar e efetuar a incluso, desperdiando o valor do primeiro encadeamento no processo. Felizmente, a interface ConcurrentMap oferece suporte para diversos mtodos adicionais projetados para executar duas tarefas com um nico bloqueio: putIfAbsent(), por exemplo, executa o teste primeiro e, em seguida, executa a incluso somente se a chave no estiver armazenada em Map.

Voltar para parte superior 5. SynchronousQueues


SynchronousQueue

um item interessante, de acordo com o Javadoc:

Uma fila de bloqueio na qual cada operao de insero deve aguardar uma operao de remoo correspondente por outro encadeamento, e vice-versa. Uma fila sncrona no possui nenhuma capacidade interna, nem mesmo a capacidade para um item. O SynchronousQueue basicamente outra implementao do supramencionado BlockingQueue. Ele fornece uma maneira leve de trocar elementos nicos de um encadeamento para outro, usando a semntica de bloqueio usada por ArrayBlockingQueue. Na Listagem 2, eu reescrevi o cdigo da Listagem 1 usando SynchronousQueue ao invs de ArrayBlockingQueue:

Listagem 2. SynchronousQueue
import java.util.*; import java.util.concurrent.*; class Producer implements Runnable { private BlockingQueue<String> drop; List<String> messages = Arrays.asList( "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "Wouldn't you eat ivy too?");

public Producer(BlockingQueue<String> d) { this.drop = d; } public void run() { try { for (String s : messages) drop.put(s); drop.put("DONE"); } catch (InterruptedException intEx) { System.out.println("Interrupted! " + "Last one out, turn out the lights!"); } } } class Consumer implements Runnable { private BlockingQueue<String> drop; public Consumer(BlockingQueue<String> d) { this.drop = d; } public void run() { try { String msg = null; while (!((msg = drop.take()).equals("DONE"))) System.out.println(msg); } catch (InterruptedException intEx) { System.out.println("Interrupted! " + "Last one out, turn out the lights!"); } } } public class SynQApp { public static void main(String[] args) { BlockingQueue<String> drop = new SynchronousQueue<String>(); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); } }

O cdigo de implementao parece quase igual, mas o aplicativo inclui um benefcio, o SynchronousQueue permitir uma insero na fila somente se houver um encadeamento aguardando para utiliz-lo. Na prtica, o SynchronousQueue similar aos "canais rendezvous" disponveis em linguagens como Ada ou CSP. Em outros ambientes, eles tambm so conhecidos como "junes" incluindo o .NET (consulte a seo Recursos).

Voltar para parte superior Concluso Por que brigar com a introduo de simultaneidade em suas classes de coleo quando a biblioteca do Java Runtime oferece equivalentes teis pr-desenvolvidos? O prximo artigo desta sria explora ainda mais o namespace java.util.concurrent .

5 things you didn't know about ... java.util.concurrent, Part 2


Concurrent programming means working smarter, not harder Ted Neward, Principal, Neward & Associates Summary: In addition to concurrency-friendly Collections, java.util.concurrent introduced other pre-built components that can assist you in regulating and executing threads in multithreaded applications. Ted Neward introduces five more of his Java programming must-haves from the java.util.concurrent package. Date: 01 Jun 2010 Level: Introductory PDF: A4 and Letter (195 KB | 9 pages)Get Adobe Reader Also available in: Chinese Korean Russian Japanese Vietnamese Portuguese Activity: 17739 views Comments: 12 (View | Add comment - Sign in) Average rating (35 votes) Rate this article Concurrent Collections make concurrent programming easier by providing thread-safe, well-tuned data structures. In some cases, however, developers need to go one step further, and think about regulating and/or throttling thread execution. Given that the whole point of java.util.concurrent is to simplify multithreaded programming, you might hope the package would include synchronization utilities and it does. This article, a follow-up to Part 1, introduces several synchronization constructs that are a higher level than the core language primitives (monitors), but not so high that they're buried inside a Collection class. Using these locks and gates is fairly straightforward once you know what they're for.

About this series

So you think you know about Java programming? The fact is, most developers scratch the surface of the Java platform, learning just enough to get the job done. In this series, Ted Neward digs beneath the core functionality of the Java platform to uncover littleknown facts that could help you solve even the stickiest programming challenges. 1. Semaphore In some enterprise systems, it's not uncommon for developers to need to throttle the number of open requests (threads/actions) against a particular resource in fact, throttling can sometimes improve the throughput of a system by reducing the amount of contention against that particular resource. While it's certainly possible to try to write the throttling code by hand, it's easier to use the semaphore class, which takes care of throttling for you, as shown in Listing 1:

Listing 1. Use Semaphore to throttle


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(); } }

Even though the 10 threads in this sample are running (which you can verify by executing jstack against the Java process running SemApp), only three are active. The other seven are held at bay until one of the semaphore counts is released. (Actually, the Semaphore class supports acquiring and releasing more than one permit at a time, but that wouldn't make sense in this scenario.)

Back to top 2. CountDownLatch

Develop skills on this topic


This content is part of progressive knowledge paths for advancing your skills. See:

Become a Java developer Java concurrency

If Semaphore is the concurrency class designed to allow threads "in" one at a time (perhaps evoking memories of bouncers at popular nightclubs), then CountDownLatch is the starting gate at a horse race. This class holds all threads at bay until a particular condition is met, at which point it releases them all at once.

Listing 2. CountDownLatch: Let's go to the races!


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(); } }

Notice in Listing 2 that CountDownLatch serves two purposes: First it releases all of the threads simultaneously, simulating the start of the race; but later a different latch simulates the end of the race, essentially so that the "main" thread can print out the results. For a race with more commentary, you could add CountDownLatches at the "turns" and "halfway" points of the race, as horses crossed the quarter, half, and threequarter values in the distance.

Back to top 3. Executor The examples in Listing 1 and Listing 2 both suffer from a fairly frustrating flaw, in that they force you to create Thread objects directly. This is a recipe for trouble because in some JVMs, creating a Thread is a heavyweight operation, and it's far better to reuse existing Threads than to create new ones. In other JVMs, however, it's exactly the opposite: Threads are pretty lightweight, and it's far better to just new-up one when you need it. Of course, if Murphy has his way (which he usually does), whichever approach you use will be exactly the wrong one for the platform you end up deploying on. The JSR-166 expert group (see Resources) anticipated this situation, to some degree. Rather than have Java developers create Threads directly, they introduced the Executor interface, an abstraction for making new threads. As shown in Listing 3, Executor allows you to create threads without having to new the Thread objects yourself:

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

The main drawback to using Executor is the same one we encounter with all factories: the factory has to come from someplace. Unfortunately, unlike the CLR, the JVM doesn't ship with a standard VM-wide thread pool.

The Executor class does serve as a common place to get Executor-implementing instances, but it only has new methods (to create a new thread pool, for example); it doesn't have precreated instances. So you are on your own if you want to create and use Executor instances throughout your code. (Or, in some cases, you will be able to use an instance provided by your container/platform of choice.) ExecutorService, at your service As useful as it is to not have to worry about where Threads come from, the Executor interface lacks some functionality that a Java developer might expect, such as the ability to kick off a thread designed to produce a result and wait in a non-blocking fashion until that result becomes available. (This is a common need in desktop applications, where a user will execute a UI operation that requires accessing a database, and yet could want to cancel the operation before it completes if it takes too long.) For this, the JSR-166 experts created a far more useful abstraction, the ExecutorService interface, which models the thread-starting factory as a service that can be controlled collectively. For example, rather than calling execute() once for each task, the ExecutorService could take a collection of tasks and return a List of Futures representing the future results of each of those tasks.

Back to top 4. ScheduledExecutorServices As great as the ExecutorService interface is, certain tasks need to be done in a scheduled fashion, such as executing a given task at determined intervals or at a specific time. This is the province of the ScheduledExecutorService, which extends ExecutorService. If your goal was to create a "heartbeat" command that "pinged" every five seconds, ScheduledExecutorService would make it as simple as what you see in Listing 4:

Listing 4. ScheduledExecutorService 'pings' on schedule


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); } }

How about that? No fussing with threads, no fussing with what to do if the user wants to cancel the heartbeat, no explicitly marking threads as foreground or background; just leave all those scheduling details to ScheduledExecutorService. Incidentally, if a user did want to cancel the heartbeat, the return from the scheduleAtFixedRate call would be a ScheduledFuture instance, which not only wraps around the result if there is one, but also has a cancel method to shut down the scheduled operation.

Back to top 5. Timeout methods The ability to put a concrete timeout around blocking operations (and thus avoid deadlocks) is one of the great advances of the java.util.concurrent library over its older concurrency cousins, such as monitors for locking. These methods are almost always overloaded with an int/TimeUnit pair, indicating how long the method should wait before bailing out and returning control to the program. It requires more work on the part of the developer how will you recover if the lock isn't acquired? but the results are almost always more correct: fewer deadlocks and more production-safe code. (For more about writing production-ready code, see Michael Nygard's Release It! in Resources.)

Back to top In conclusion The java.util.concurrent package contains many more nifty utilities that extend well beyond Collections, particularly in the .locks and .atomic packages. Dig in and you'll also find useful control structures like CyclicBarrier and more. Like many aspects of the Java platform, you don't need to look very hard to find infrastructure code that can be tremendously helpful. Whenever you're writing multithreaded code, remember the utilities discussed in this and the previous article. Next time, we'll branch out into a new topic: five things you didn't know about Jars.

Das könnte Ihnen auch gefallen