Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


ConcurrentModificationException e os fail-fast iterators

Por Paulo Silveira em 18/08/10

A java.util.ConcurrentModificationException costuma surpreender a muitos: como uma exception com esse nome pode aparecer mesmo em uma aplicação single threaded, que não envolve concorrência alguma no acesso dessa coleção?

Para entender melhor, vale relembrar que as coleções muito antigas, como Vector e Hashtable, são thread safe, implementado através do uso do synchronized em seus métodos e iteradores (Enumeration na época). No Java 1.2, com a entrada das interfaces Collection, List, Set e Iterator, as novas implementações optaram por não ser thread safe, dado o custo de performance que o synchronized apresentava (hoje em dia é muito, muito menor), e de que a grande maioria dos casos de uso dessas estruturas não necessitavam de thread safety.

Essa novidade, das novas coleções não serem preparadas para o uso em um ambiente multi thread, poderia causar surpresas para quem não a estivesse esperando. Em vez de deixar alguém percorrer uma coleção através de um Iterator enquanto ela é modificada concorrentemente (acarretando em dados incorretos, nulls, etc) optou-se por evitar esses casos. Para isso tenta-se “adivinhar” quando houver mais de uma thread trabalhando com a mesma coleção concorrentemente (uma modificando a coleção, e outra iterando-a).

Como fizeram isso sem usar mecanismos de lock? Usando um contador!

Imagine que a cada modificação na estrutura de uma ArrayList, nós incrementamos um contador (modCount). Quando um Iterator é requisitado pelo método iterator(), esse contador é guardado como atributo (expectedModCount). Cada vez que você invocar os métodos next() e remove() (lembrando que temos apenas 3 métodos na interface Iterator), esse iterador vai checar se o contador da ArrayList é exatamente igual ao número que era esperado, isso é, tem o mesmo valor desde quando começamos a percorrer seus elementos. Caso os valores sejam diferentes, a java.util.ConcurrentModificationException é lançada, pois foi detectada uma modificação concorrente (comodification).

Mas como essa exceção pode ocorrer mesmo quando não temos outras threads acessando a mesma coleção? O problema é exatamente essa tentativa de detectar um acesso concorrente:

List<String> nomesDosAlunos = new ArrayList<String>();
// ...  popula lista com strings
for (String nome : nomesDosAlunos) {
	if (nome.equals("nome procurado")) {
		nomesDosAlunos.remove(nome);
	}
}

Esse código vai lançar ConcurrentModificationException caso encontre a String procurada:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
	at java.util.AbstractList$Itr.next(AbstractList.java:343)
	...

Isso ocorre pois o Iterator utilizado internamente nesse laço vai detectar, na próxima chamada ao seu método next(), que o número de modificações desta ArrayList é diferente de quando ele foi instanciado. Essa stacktrace pode confundir um pouco, já que o uso do enhanced for esconde a utilização do iterator, e não conseguimos ver explicitamente a invocação do método next na linha do for.

Repare que a detecção nesse caso single threaded é arbitrária para evitar casos estranhos (se removessemos o objeto da lista, o iterator deveria ainda percorre-lo?), e coleções sem fail fast iterators vão possibilitar esse tipo de remoção e outras modificações sem lançar excessões. Como então evitar essa exception nas coleções com fail fast iterators? Utilizando o iterator.remove() em vez do enhanced for:

List<String> nomesDosAlunos = new ArrayList<String>();
// ...  popula lista com strings
for (Iterator<String> i = nomesDosAlunos.iterator(); i.hasNext();) {
	String nome = i.next();
	if (nome.equals("nome procurado")) {
		i.remove();
	}
}

Esses iteradores são chamados de fail-fast por possuirem essa característica de falhar quando uma modificação concorrente é detectada. Essa é apenas uma tentativa do Iterator em encontrar possíveis bugs, e existem casos e combinações em que a modificação concorrente pode passar desapercebida por esse mecanismo, e você não deve se basear nessa exception para garantir que sua aplicação é thread safe.

E se precisarmos usar uma lista em ambiente multi thread e percorrer seu iterator enquanto a modificamos? Podemos usar a CopyOnWriteArrayList (nos casos de muita leitura e pouca escrita) ou ainda, se não for precisar de acesso aleatório aos elementos, utilizar uma Queue como a ConcurrentLinkedQueue. Utilizar o antigo Vector ou a própria ArrayList em conjunto com Collections.synchronizedList é thread safe e não vai lançar ConcurrentModificationException desde que você sincronize o uso de seus iterators:

synchronized(nomesDosAlunos) {
	for (String nome : nomesDosAlunos)
		System.out.println(nome);
}

A performance dessas diferentes escolhas pode mudar drasticamente dependendo do contexto de sua aplicação. Pode também não ser necessário utilizar uma coleção que se preocupa com thread safety: algumas vezes basta diminuir o escopo da coleção (pode não ser necessário deixá-la na session, por exemplo) ou ainda trabalhar com cópias defensivas.

  • Share/Bookmark

Possibilidades de design no uso do seu Generic DAO

Por Lucas Cavalcanti em 26/07/10

Muitas vezes, quando estamos criando nosso sistema temos a tentação de criar o GenericDAO para não ter que ficar repetindo as operações CRUD e listagens.

O maior problema com o GenericDAO é que não necessariamente todas as operações fazem sentido para uma determinada classe. Daí o que fazer se, por exemplo, não faz sentido excluir um pagamento?

public class PagamentoDAO
          extends GenericDAO<Pagamento> {

     @Override
     public void excluir(Pagamento pagamento) {
         throw new UnsupportedOperationException();
     }
}

Não parece uma solução muito elegante, mas é um dos únicos jeitos de proibir uma operação declarada na classe mãe, e ainda assim, só funciona em tempo de execução. Esse é um dos principais motivos para muitos não gostarem de usar o GenericDAO e preferirem usar composição ao invés de herança. Mas como fazer para não repetir o código trivial das operações do CRUD?

Um dos jeitos é usar uma outra abstração de persistência de objetos: o Repository. Com o Repository, temos um lugar onde podemos guardar e buscar por objetos, não importando como fazemos isso. O DAO já está muito ligado com armazenamento em banco de dados, e foi criado quando as operações do BD eram muito trabalhosas (em especial no JDBC).

E como juntar o Repository com o GenericDAO? O Repository pode ser definido, por exemplo, como uma interface, e aí podemos fazer o seguinte: se, para um pagamento, faz sentido apenas salvar e listar, mas não excluir, então criamos a interface:

public interface PagamentoRepository {
    void salva(Pagamento pagamento);
    Pagamento busca(Long id);
    List<Pagamento> lista();
}

E usamos o GenericDAO como implementação dessa interface:

class PagamentoDAO
          extends GenericDAO<Pagamento>
          implements PagamentoRepository {
   // implementacao extra
}

E no nosso código de domínio “nunca” referenciaremos o PagamentoDAO, apenas o PagamentoRepository, assim mesmo que a implementação saiba fazer mais coisas, a interface só expõe as operações suportadas.

Se você usa Injeção de Dependências e algum framework que a suporta (como o VRaptor, Spring ou Java EE6), você pode deixar os DAOs apenas como infraestrutura, e usar os Repositories como interfaces públicas da sua aplicação:

public class PagamentoController {
    public PagamentoController(PagamentoRepository repository) {
         this.repository = repository;
    }

    public void salva(Pagamento pagamento) {
         // validações e outras regras
         repository.salva(pagamento);
    }
}

Ainda poderíamos melhorar o nome do nosso repositório para BaseDePagamentos, ContasAPagar ou ainda Pagamentos, evitando usar sufixos nas classes. Usando abstrações e padrões simples conseguimos evitar repetição de código sem perder a semântica e restrições das nossas classes de modelo, além de esconder detalhes de implementação.

  • Share/Bookmark

Então você quer ser um arquiteto Java?

Por Paulo Silveira em 21/07/10

Durante o atual processo de revisão do livro de Arquitetura e Design de Software, discussões apareceram sobre o termo arquiteto. Antes de definir o que faz um arquiteto, há o termo arquitetura.

Quem é o arquiteto? Aquele que senta sozinho e toma todas as grandes decisões?

O que é a arquitetura de uma aplicação?
Uma pergunta difícil de responder. Entre as definições mais antigas, Roy Fielding possui um bom texto no primeiro capítulo de sua dissertação de doutorado. O Instituto de Engenharia de Software da Universidade de Carnegie Mellon apresenta diferentes definições, algumas clássicas e bastante conhecidas, como “arquitetura é a estrutura do sistema, composta de componentes, as propriedades que são visíveis externamente desses componentes e o relacionamento entre eles“.

Nas palavras de Martin Fowler, “o termo arquitetura envolve a noção dos principais elementos do sistema, as peças que são difíceis de mudar. Uma fundação na qual o resto precisa ser construído“. Fowler reformula sua definição de arquitetura e a define como “as peças que as pessoas acham que é difícil de mudar“. No mesmo artigo Ralph Johnson, do GoF, diz que arquitetura “é o conjunto de decisões de design que gostaríamos de ter feito no começo do projeto” e termina com uma definição mais abrangente: “arquitetura é tudo aquilo que importa“. Com tantas definições, talvez seja mais fácil diferenciarmos design de arquitetura.

Qual é a diferença de design e arquitetura de software?
Aqui também temos uma resposta clássica na literatura: a arquitetura é responsável pelos requisitos não-funcionais, e o design pelos funcionais. Mas parece que essa distinção não é tão clara assim para muitos outros autores.

Neal Ford apresenta uma distinção simples, realçando que o design é feito em cima do que foi decidido pela arquitetura, e por isso o que faz parte da arquitetura é mais difícil de mudar. Devemos minimizar as peças que dificultam mudanças do nosso design, mas é impossível eliminar todas, além de que flexibilidade sempre vem a um custo de complexidade.

É difícil criar um distinção maior entre os dois. No livro Patterns of Enterprise Application Architecture, Fowler diz que “alguns dos padrões nesse livro podem ser chamados arquiteturais, já que representam decisões importantes sobre essas partes; outros são mais sobre design e te ajudam a implementar essa arquitetura. Eu não faço nenhuma tentativa forte de separar esses dois, já que é o que é arquitetural ou não é subjetivo“.

O arquiteto deve saber programar na plataforma em questão?
Sem dúvida. Cada vez mais vemos que o design e a implementação devem ser trabalhados juntos. A imagem de um arquiteto distante sem profundo conhecimento técnico que apenas toma as grandes decisões ficou pra trás: conhecimento técnico e a capacidade de liderança são as características fundamentais.

Mais do que querer ser o poderoso arquiteto que apenas despacha ordens e toma todas as grandes decisões, cada vez mais enxergamos que o caminho é ser o líder que incentiva essa tomada de decisão, além de ser um exímio programador. Parafraseando mais uma vez Martin Fowler, “…o arquiteto deve ser como um guia… que é um experiente e capacitado membro da equipe que ensina aos outros a melhor se virarem, ainda assim ele está sempre lá para as partes mais complicadas“.

Vale lembrar que precisamos de mais de 10 mil horas, ou 10 anos, para dominar uma linguagem.

  • Share/Bookmark

Arredondamento no Java: do double ao BigDecimal

Por Paulo Silveira em 15/07/10

É fácil se deparar com as limitações do double no Java e na maioria das outras linguagens: quando vamos trabalhar com dinheiro notamos que as contas não estão saindo exatamente como esperávamos:

double d1 = 0.1;
double d2 = 0.2;
System.out.println(d1 + d2);

O resultado é um estranho 0.30000000000000004, que pode acarretar em problemas graves dependendo da utilização e arrendondamento aplicado depois nesse número. O problema é que um número com 0.1 não pode ser representado em binário de maneira finita: ele vira uma dízima (no binário ficaria algo como 0.110011001100…) diferente do número 0.25, que pode ser representado perfeitamente (no binário 0.01). A representação é um pouco mais complicada que isso, a JVM segue o padrão IEEE 754 para trabalhar com números de ponto flutuante.

Como obter o esperado 0.3? A sugestão sempre é usar o BigDecimal. BigDecimal é uma classe que trabalha com números de ponto flutuante de precisão arbitrária: você pode escolher quanto de precisão você quer usar. Por padrão ele vai utilizar o que for necessário, e, diferente do double, ele consegue guardar números como 0.1, pois guardará isto como sendo 1 x 10ˆ-1 (isto é, usando a base decimal em vez de binária, evitando a dízima).

// nao use esse construtor:
BigDecimal big1 = new BigDecimal(0.1);
BigDecimal big2 = new BigDecimal(0.2);

System.out.println(big1.add(big2));

O resultado é uma nova surpresa, um incrível 0.300000000000000016653345369377.... O que fizemos de errado agora foi que tentar somar 0.1 e 0.2 sendo que esses dois números já estavam armazenados em memória como double, e, ao serem passados para o construtor do BigDecimal, foram transportados com imprecisão. O próprio javadoc desse construtor diz que “The results of this constructor can be somewhat unpredictable“. Na verdade o resultado é bem previsível pelas suas regras, mas não é o que gostaríamos.

Como resolver? Basta sempre usar o construtor que trabalha com Strings, assim o BigDecimal vai internamente fazer o parsing desses números sem que eles sejam armazenados em um double, evitando os problemas de precisão:

// atencao! usando String no construtor:
BigDecimal big1 = new BigDecimal("0.1");
BigDecimal big2 = new BigDecimal("0.2");

System.out.println(big1.add(big2));

Finalmente obtendo o resultado esperado. Há ainda importantes observações sobre o BigDecimal: por padrão ele não fará nenhum tipo de arredondamento, o que o obriga a lançar java.lang.ArithmeticException no caso de uma dízima decimal (tentar dividir 1/3 por exemplo). Nesses casos é necessário delimitar a quantidade de bits a serem usados ou escolher o modo de arredondamento:

BigDecimal big1 = new BigDecimal("1");
BigDecimal big2 = new BigDecimal("3");

System.out.println(big1.divide(big2, 3, RoundingMode.UP));

Resultando em 0.334. Vale lembrar também da imutabilidade da classe BigDecimal, que traz diversas vantagens mas deve ser usada com cuidado quando diversas operações serão realizadas em cima de um mesmo número dentro de um laço, já que diversos BigDecimals serão instanciados durante a operação, podendo acarretar no mesmo problema de performance do uso de concatenação de Strings. O Donizetti lembrou que esse assunto é bastante discutido no item 48 do Effective Java.

No JavaScript teremos o mesmo problema caso você precise realizar contas no lado do cliente, e aí podemos usar a BigDecimalJS, sugerida pelo Jefferson Girão, que funciona de maneira análoga ao Java.

O Rafael Ferreira lembra que podemos ir além, e como dinheiro é algo pertencente ao nosso domínio e lógica de negócios, criamos uma classe Money para encapsular todo esse comportamento e evitar que RoundingMode, MathContext e escalas se espalhem por todo seu código.

  • Share/Bookmark

Compondo seu comportamento: herança, Chain of Responsibility e Interceptors

Por Guilherme Silveira em 28/06/10

São diversos os momentos em que temos a tentação de usar herança para implementar funcionalidades de maneira rápida. Um exemplo simples é o polêmico caso de Properties e Hashtable em Java.

Alguns padrões também costumam ser implementados através de herança são cadeias de responsabilidade, decorators, template method, filtros/interceptadores, entre outros. O exemplo a seguir mostra a adição de comportamento através de herança próxima ao que acontece com uma Servlet, ou ainda um InputStream que delega para outro:

class Server {
  Response service(Request req) {
     return new Response("<html>get para " + req.getPath() + "</html>");
  }
}

class CacheableServer extends Server {
  Response service(Request req) {
     Response resp = super(req);
     resp.addHeader("Cache-control", "public, max-age=7200");
     return resp;
  }
}

class LoggingServer extends CacheableServer {
  Response service(Request req) {
     Logger.debug("Requesting " + req.getPath());
     return super(req);
  }
}

Encadear comportamentos através de herança de classes apresenta diversos problemas de acoplamento e dificulta a customização de um processo.

Se eu desejasse um servidor com log mas sem as características de Cache, deveria criar uma quarta classe, LoggingWithoutCacheServer que herda de Server e reescrever o código de log. Costuma-se então sugerir a utilização de herança para evitar copiar-e-colar de código, mas podemos ver que somente com ela isso não é verdade.

Herança de classes é positivo por causa do polimorfismo, mas cria uma acoplamento perigoso, já discutido por diversos autores, em especial nos livros Effective Java e Design Patterns.

Em Ruby temos uma abordagem semelhante de reutilização de comportamento através da herança de classe ou através da inclusão de módulos:

module ServiceProvider
  def service(req)
    Response.new "<html>get para #{req.path}</html>"
  end
end

module CacheableProvider
  def service(req)
    res = super(req)
    res.headers["Cache-control"] = "public, max-age=7200"
    res
  end
end

module LoggingProvider
  def service(req)
     Logger.debug "Requesting #{req.path}"
     super(req)
  end
end

E agora podemos descrever nosso serviço através da inclusão dos módulos, na ordem que for necessária:

class Server
  include ServiceProvider
  include CacheableProvider
  include LoggingProvider
end

Se desejamos uma outra ordem de execução qualquer, basta mudar a ordem ou adicionar novos módulos que delegam para super quando desejado. Aqui ainda temos problemas de herança apresentados no post de 2006: o acoplamento da classe Server com os módulos é muito forte e, pior ainda, criamos dependências implicitas entre os módulos que antes não se conheciam: se um dos providers de log possui um método chamado info e o outro provider possui outro método info, sua aplicação não funcionará como o esperado.

A inclusão de módulos para compor a herança, seja em tempo de codificação ou execução, mantem o acoplamento alto. Note que ambas as abordagens permitem a adição de novo comportamento ou encadeamento dos mesmos visando minimizar a atualização na classe filha, mas acabando por necessitar que o autor de cada módulo conheça os detalhes dos outros, aumentando o acoplamento.

Favorecer composição de comportamentos através da agregação de objetos diminui o acoplamento entre os mesmos. Em Java teríamos:

interface Server {
  Response service(Request req);
}
class DefaultServer implements Server {
  public Response service(Request req) {
     return new Response("<html>get para " + req.getPath() + "</html>");
  }
}
class CacheableServer implements Server {
  private Server delegate;
  CacheableServer(Server delegate) {
    this.delegate = delegate;
  }
  public Response service(Request req) {
     Response resp = delegate.service(req);
     resp.addHeader("Cache-control", "public, max-age=7200");
     return resp;
  }
}
class LoggingServer implements Server {
  private Server delegate;
  LoggingServer(Server delegate) {
    this.delegate = delegate;
  }
  public Response service(Request req) {
     Logger.debug("Requesting " + req.getPath());
     return delegate.service(req);
  }
}

E a criação de nosso serviço ou processo pode ser totalmente customizada:

Server log = new LoggingServer(new Server());
Server logAndCache =
  new LoggingServer(new CachingServer(new DefaultServer()));

Para quem gostaria de utilizar essa prática de compor o processamento de uma requisição em diversas fases, como as empresas de host, a funcionalidade de Valves do Tomcat em 2004 permitia que criassem filtros, anteriormente possuindo apis não publicadas em sua versão 3, exatamente como aqueles que o middleware Rack faz hoje em dia:

module Rack
  class CustomLog
    def initialize app
      @app = app
    end

    def call env
      status, headers, body = @app.call env
      if env['HTTP_METHOD']=='HEAD'
        body = nil
      end
      [status, headers, body]
    end
  end
end

Em linguages funcionais como Javascript ou com suporte a ponteiros de função, como C, podemos criar o mesmo tipo de composição de comportamento:

server = function (req) {
  // return nova resposta
}

function support_log(base) {
  return function(req) {
    var res = base(req);
    console.log("logando o resultado");
    return req;
  }
}

function support_head(base) {
  return function(req) {
    var res = base(req);
    if(req.method == "HEAD"){
      res.body = "";
    }
    return res;
  }
}

function parse(req) {
  return support_head(support_log(server))(req);
}

Note que em todos os casos, assim como na sequência de converters e wrappers do XStream, favorecemos composição ao invés de herança.

Ao mesmo tempo, encontramos uma dificuldade na hora de implementar a composição: como compartilhar objetos para serem acessados entre diferentes comportamentos que foram adicionados ao nosso objeto original?

A solução mais encontrada em todas as linguagens mencionadas é a criação de um escopo novo contendo todos os objetos a serem compartilhados (contexto), um objeto que funciona como um mapa. Em linguagens com tipagem estática isso pode implicar em um perigo a mais, um cast forçado:

  public Response service(Request req, Context ctx) {
     User user = (User) ctx.get("logged_in_user");
     Logger.debug("Requesting " + req.getPath());
     return delegate.service(req);
  }

Essa é a solução adotada pelo servlet API, além de ser adotado pela implementação do Rack middleware com o env.

Uma outra solução pouco utilizada e que poderia aumentar a legibilidade em troca de menos visibilidade é fazer o lazy load de acesso a variável através do method_missing, algo como:

def method_missing(sym, args)
  return env[sym] if env.include?(sym.to_s)
  super(sym, args)
end

Compor um comportamento através de outros é fundamental para diminuir a complexidade de métodos e classes. Podemos fazer isso através de herança, pagando-se um preço alto, ou utilizar chain of responsability, interceptors e outros padrões, que podem ser implementados puramente através de interfaces e composição.

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222