C'est une question premièrement d'équilibre temps d'exécution/mémoire et deuxièmement de monothread versus multithread (accès concurrents). Et, donc, ça dépend de ce que tu vas avoir besoin de faire.
Une ArrayList utilise un tableau en interne pour stocker les éléments, ce qui est très peu coûteux en mémoire. Un tableau étant de taille fixe, s'il n'y a plus assez de place, un nouveau tableau est créé dans lequel on copie l'ancien (donc tous les éléments sont copiés), et qui prend ensuite la place de l'ancien. Ajouter un élément tant qu'il y a de la place est très peu coûteux en temps : on copie la référence de l'élément dans une case de tableau et on incrémente un index. Insérer au milieu (ou au début) oblige à décaler les éléments, donc à les copier. En revanche parcourir le tableau se fait par un for(int i=0; i<tableau.length; i++) qui ne coûte que très peu en temps. Même avec un Iterator, le coût est réduit.
En résumé une ArrayList est très efficace pour un coût minime en mémoire lorsqu'on ajoute ou supprime pas souvent des éléments. Si après que le modèle soit rempli initialement, il n'y a ni ajout ni suppression de ligne, prends une ArrayList.
Une LinkedList utilise des wrappers (node) pour stocker les éléments, qui sont liés entre eux. Le coût en mémoire est donc plus élevé que pour un élément de tableau (il faut de toute façon stocker la référence, mais aussi les liens). Supprimer un élément est très peu coûteux en temps, puisqu'il suffit de couper des liens. Ajouter un élément n'est pas beaucoup plus coûteux. En revanche, accéder à un élément directement par son index peut être relativement coûteux parce qu'il faut parcourir les nodes de nodes en nodes par les liens : don très peu pour les bas index, mais de plus en plus coûteux pour les index élevés (toutefois par Iterator, le parcourt est plus rapide, parce qu'on va toujours uniquement au node suivant). Le modèle de table donnant un accès aux données par une méthode getValueAt() qui prend deux index en paramètre, un pour la ligne, un pour la colonne, le temps d'accès, donc d'affichage, peu être particulièrement impacté s'il y a beaucoup de lignes. S'il y a peu de lignes, le temps d'accès peu être acceptable, s'il y a beaucoup de lignes, ça peut devenir problématique.
Une ConcurrentLinkedQueue n'a d'intérêt que si tu as besoin de faire des ajouts, suppressions de ligne en parallèle, avec plusieurs threads (par exemple, tu as un système qui remplit la table petit à petit en faisant plusieurs requête, ou un système de notifications qui de temps à autres va récupérer des nouvelles lignes). Seulement tu est censé appeler la méthode addRow uniquement dans l'Event Dispatch Thread. Donc ça n'a aucun intérêt et ça introduirait du temps de cpu (consommé par la gestion de la concurrence d'accès) inutilement. Ou alors il faudrait un système intermédiaire complexe (un sas) à base de ExecutorService. Une autre option serait de gérer uniquement l'aspect événementiel par ExecutorService, mais si c'est relativement simple si on ne fait que des ajouts, la concurrence avec de la suppression me semble à priori impossible (à cause des décalages d'index vers le 0). En résumé, envisager ce genre de stockage t'engage sur beaucoup de travail, de prises de tête et aucune assurance d'avoir un résultat qui fonctionne à la fin.
Il y a une multitude d'autres implémentations possibles de List, par ailleurs, qui présentent toutes des avantages et des inconvénients. On pourrait même utiliser une Map.
En résumé, utiliser une ArrayList est le choix de base. Ensuite, tu verras bien si les performances posent parfois des soucis et tu adapteras. On n'est d'ailleurs pas obliger nécessairement de changer d’implémentation de liste. On peut gérer plus finement l'agrandissement de l'ArrayList afin d'optimiser : par exemple, si tu peux connaître une estimation d'un maximum de lignes (ou d'un seuil moyen) et que tu as de la mémoire, tu peux des le départ dimensionner le tableau à une taille importante, pour éviter d'avoir à absorber des agrandissements (donc copies) multiples (par exemple, en comptant les résultats dans le ResultSet, tu peux dimensionner l'ArrayList (méthode ensureCapacity si l'instance existe déjà).
Et on peut bien sûr même implémenter sa propre implémentation de List.
Partager