Como posso tornar meu ArrayList seguro para threads? Outra abordagem para o problema em Java?


90

Eu tenho uma ArrayList que desejo usar para conter objetos RaceCar que estendem a classe Thread assim que sua execução for concluída. Uma classe, chamada Race, trata este ArrayList usando um método de retorno de chamada que o objeto RaceCar chama quando termina de ser executado. O método de retorno de chamada, addFinisher (RaceCar finisher), adiciona o objeto RaceCar ao ArrayList. Isso deve fornecer a ordem na qual os Threads terminam de ser executados.

Eu sei que ArrayList não está sincronizado e, portanto, não é seguro para thread. Tentei usar o método Collections.synchronizedCollection (c Collection) passando uma nova ArrayList e atribuindo a coleção retornada a uma ArrayList. No entanto, isso me dá um erro de compilador:

Race.java:41: incompatible types
found   : java.util.Collection
required: java.util.ArrayList
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

Aqui está o código relevante:

public class Race implements RaceListener {
    private Thread[] racers;
    private ArrayList finishingOrder;

    //Make an ArrayList to hold RaceCar objects to determine winners
    finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

    //Fill array with RaceCar objects
    for(int i=0; i<numberOfRaceCars; i++) {
    racers[i] = new RaceCar(laps, inputs[i]);

        //Add this as a RaceListener to each RaceCar
        ((RaceCar) racers[i]).addRaceListener(this);
    }

    //Implement the one method in the RaceListener interface
    public void addFinisher(RaceCar finisher) {
        finishingOrder.add(finisher);
    }

O que preciso saber é se estou usando uma abordagem correta e, se não, o que devo usar para tornar meu código thread-safe? Obrigado pela ajuda!


2
(Observe que a Listinterface não é realmente completa o suficiente para ser muito útil em multithreading.)
Tom Hawtin - tackline

3
Gostaria apenas de salientar que, sem Collections.synchronizedList(), teríamos uma condição de corrida REAL aqui: P
Dylan Watson

Respostas:


145

Use Collections.synchronizedList().

Ex:

Collections.synchronizedList(new ArrayList<YourClassNameHere>())

2
Obrigado! Não sei por que não pensei em apenas usar um Vector, pois me lembro de ter lido em algum lugar que eles estavam sincronizados.
ericso

32
Talvez não seja uma boa ideia trabalhar com classes definidas como obsoletas
frand

1
Embora o Vector seja muito antigo e não tenha suporte para coleções, ele não está obsoleto. Provavelmente é melhor usar Collections.synchronizedList () como outras pessoas disseram aqui.
Astúrio

14
-1 para comentários. O Vector não está obsoleto e como ele não tem suporte para coleções? Ele implementa Lista. O javadoc para Vector diz especificamente: "A partir da plataforma Java 2 v1.2, esta classe foi adaptada para implementar a interface List, tornando-a um membro do Java Collections Framework. Ao contrário das novas implementações de coleção, Vector é sincronizado." Pode haver boas razões para não usar Vector (evitando sincronização, mudando implementações), mas ser "obsoleto" ou "não moderno" não é uma delas.
silly4jesus

1
Use os métodos abaixo: Collections.synchronizedList (list); Collection.synchronizedSet (set); Collections.synchronizedMap (mapa); Os métodos acima tomam a coleção como parâmetro e retornam o mesmo tipo de coleção que são sincronizados e thread-safe.
Sameer Kazi

35

mudança

private ArrayList finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars)

para

private List finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedList(new ArrayList(numberOfRaceCars)

List é um supertipo de ArrayList, então você precisa especificar isso.

Caso contrário, o que você está fazendo parece bom. Outra opção é que você pode usar o Vector, que é sincronizado, mas provavelmente é o que eu faria.


1
Ou Listprovavelmente seria mais útil. Ou List<RaceCar>.
Tom Hawtin - tackline

Bom ponto, torne-o privado List finishOrder = Collections.synchronizedList (...)
Reverendo Gonzo

Eu tentei isso e o compilador agora está reclamando sobre eu chamar métodos ArrayList em uma coleção: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); ele está dizendo que o método get (0) não foi encontrado. Pensamentos?
ericso

Desculpe por excluir e adicionar novamente meu comentário. Eu estava tentando fazer o destaque funcionar usando backticks. Eu tenho TOC por causa desse tipo de coisa.
ericso

Não, isso não funciona. Não vai lançar a coleção para uma lista: Race.java:41: tipos incompatíveis encontrados: java.util.Collection necessário: java.util.List finishOrder = Collections.synchronizedCollection (new ArrayList (numberOfRaceCars));
ericso

11

CopyOnWriteArrayList

Use a CopyOnWriteArrayListclasse. Esta é a versão de thread segura ArrayList.


3
Pense duas vezes ao considerar esta classe. Para citar o documento da classe: ”Isso normalmente é muito caro, mas pode ser mais eficiente do que as alternativas quando as operações de passagem superam em muito as mutações e é útil quando você não pode ou não deseja sincronizar as passagens, mas precisa impedir a interferência entre threads simultâneos . ” Além disso, consulte a diferença entre CopyOnWriteArrayList e synchronizedList
Basil Bourque

essa classe entra em ação quando você raramente modifica a lista, mas frequentemente itera sobre os elementos. por exemplo, quando você tem um conjunto de ouvintes. você os registra e então itera muito ..., se você não precisa explicitamente da interface de lista, mas modifica e lê as operações para serem simultâneas, considereConcurrentLinkedQueue
benez

7

Você pode estar usando a abordagem errada. Só porque um thread que simula um carro termina antes de outro thread de simulação de carro, não significa que o primeiro thread deve vencer a corrida simulada.

Depende muito da sua aplicação, mas pode ser melhor ter um thread que calcule o estado de todos os carros em pequenos intervalos de tempo até que a corrida seja concluída. Ou, se preferir usar vários tópicos, você pode fazer com que cada carro registre o tempo "simulado" que levou para completar a corrida e escolher o vencedor como aquele com o menor tempo.


Este é um bom ponto. Este é apenas um exercício de um texto que estou usando para aprender Java. O objetivo era aprender a usar threads e, na verdade, estou indo além das especificações originais do problema ao construir um mecanismo para registrar os vencedores. Pensei em usar um cronômetro para medir os vencedores. Mas, honestamente, acho que consegui o que preciso com o exercício.
ericso

5

Você também pode usar uma synchronizedpalavra-chave para um addFinishermétodo como este

    //Implement the one method in the RaceListener interface
    public synchronized void addFinisher(RaceCar finisher) {
        finishingOrder.add(finisher);
    }

Portanto, você pode usar o método ArrayList add thread-safe desta forma.


4
bem, mas e se você tiver dois métodos: addFinisher e delFinisher? Ambos os métodos são thread-safe, mas como ambos acessam a mesma ArrayList, você ainda terá problemas.
masi

1
@masi Então você apenas sincroniza em um final Objectsempre que acessar de Collectionalguma forma.
mkuech

2

Sempre que você quiser usar a versão ant thread safe do objeto de coleção de formigas, peça ajuda do pacote java.util.concurrent. * . Ele tem quase todas as versões simultâneas de objetos de coleção não sincronizados. por exemplo: para ArrayList, você tem java.util.concurrent.CopyOnWriteArrayList

Você pode fazer Collections.synchronizedCollection (qualquer objeto de coleção), mas lembre-se deste synchr clássico. a técnica é cara e vem com sobrecarga de desempenho. pacote java.util.concurrent. * é menos caro e gerencia o desempenho de uma maneira melhor usando mecanismos como

cópia na gravação, comparação e troca, bloqueio, iteradores de instantâneo, etc.

Portanto, prefira algo do pacote java.util.concurrent. *


1

Você também pode usar como Vector em vez disso, já que os vetores são thread-safe e arraylist não. Embora os vetores sejam antigos, eles podem resolver seu propósito facilmente.

Mas você pode fazer seu Arraylist sincronizado como código, dado o seguinte:

Collections.synchronizedList(new ArrayList(numberOfRaceCars())); 

-1

Você pode mudar de ArrayList para o tipo Vector, em que todos os métodos são sincronizados.

private Vector finishingOrder;
//Make a Vector to hold RaceCar objects to determine winners
finishingOrder = new Vector(numberOfRaceCars);

5
Se você vai sugerir o uso de outra coleção, provavelmente Vector é uma escolha ruim. É uma coleção de legado que foi adaptada ao design do novo Java Collections Framework. Tenho certeza de que existem melhores opções no pacote java.until.concurrent.
Edwin Dalorzo
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.