Minor sequence:
First of all, there is a question, why do you choose collections? What convenience does its birth provide for programmers?
To clarify this, we have to start with the array; When guests come to our house, they usually take out fruits to entertain, but they don't just take one each time. Go and get it after eating? That would be too troublesome and impolite! We usually take a plate to hold fruit. Just enjoy the guests. This plate is equivalent to an array, which is used to carry data.
But the array is not perfect. It can only add the same type every time it adds data, and the size is fixed. Put it on the plate, that is, it can only contain one kind of fruit at a time, and the number is limited! People who have fewer plates at home are a little stretched.
But these problem sets can be easily solved. There is only one plate at home. No problem. You can put a variety of fruits on one plate. What? What if the plate is not big enough? It doesn't matter. Just tell me how many fruits you want. I can expand my capacity to hold all the fruits! How did you do that? I think the source code can do it 😄
Text:
ArrayList
Underlying data structure: array
Default initial capacity: 0
Capacity expansion: current array length * 1.5
Thread safe: unsafe
Features: fast query, slow addition and deletion
Next, we explain the above features from the source code
- DemoTest class
package com.zhao.Collection; import java.util.ArrayList; @SuppressWarnings({"all"}) //Suppress warning messages public class ArrayListTest { public static void main(String[] args) { ArrayList arrayList = new ArrayList(); //Create array for (int i = 1; i <= 10; i++) { arrayList.add(i); //Add data 1 ~ 10 } for (int i = 11; i <= 15; i++) { arrayList.add(i); //Add data 11 ~ 15 } arrayList.add(100); arrayList.add(200); arrayList.add(null); } }
- ArrayList parameterless construction part of the source code
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import sun.misc.SharedSecrets; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ //Used to verify the consistency of versions private static final long serialVersionUID = 8683452581122892189L; //Default initial capacity: 10 private static final int DEFAULT_CAPACITY = 10; //(parameterized construction) empty array private static final Object[] EMPTY_ELEMENTDATA = {}; //(parameterless construction) default empty array private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //Element data. Variables modified by the transient keyword are not serialized or deserialized transient Object[] elementData; //Array length private int size; //Nonparametric structure public ArrayList() { //Initialize the collection and assign an empty array to the collection this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } }
- At this time, the collection has been initialized, and the collection length is 0. When the add (integer i) method is executed for the first time, the source code is as follows
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import sun.misc.SharedSecrets; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ //Used to verify the consistency of versions private static final long serialVersionUID = 8683452581122892189L; //Default initial capacity: 10 private static final int DEFAULT_CAPACITY = 10; //(parameterized construction) empty array private static final Object[] EMPTY_ELEMENTDATA = {}; //(parameterless construction) default empty array private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //Element data. Variables modified by the transient keyword are not serialized or deserialized transient Object[] elementData; //The maximum value of int is 2147483639 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //Array length private int size; //Nonparametric structure public ArrayList() { //Initialize the collection and assign an empty array to the collection this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //Add element for the first time public boolean add(E e) { //At this time, the default size is 0; (0+1 = 1 ) ensureCapacityInternal(size + 1); //The capacity expansion is realized by calling the ensureCapacityInternal method //Add an element to the 0 index in the array (size + +, after + +, the size is still 0) elementData[size++] = e; //Add successfully, return true return true; } //Ensure sufficient collection capacity private void ensureCapacityInternal(int minCapacity) { //Parameter 1: elementData initialized {} //Parameter 2: minCapacity is the minimum required capacity. At this time, only one element is added, so the value is 1; ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //See how much capacity the collection requires at least private static int calculateCapacity(Object[] elementData, int minCapacity) { // {} = {} is true, and the method knows that it is the first time to add an element if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //DEFAULT_ The default value of capacity is 10 and the minCapacity value is 1 It has been analyzed above //Returns the largest of the two numbers. ten return Math.max(DEFAULT_CAPACITY, minCapacity); } //Returns the minimum capacity value return minCapacity; } //Determine whether capacity expansion is required //Parameter: at this time, the reference value of the minimum capacity is 10, minCapacity = 10; private void ensureExplicitCapacity(int minCapacity) { modCount++; //Modification times //elementData.length = 0ï¼› (because it is an empty collection) //If 10 - 0 > 0, the grow() method is called and the minCapacity value is passed into the method if (minCapacity - elementData.length > 0) grow(minCapacity); } //Real collection expansion operation private void grow(int minCapacity) { //Record the length of the set. At this time, the set is empty, the length is 0 by default, and oldCapacity = 0; int oldCapacity = elementData.length; //oldCapacity > > 1, shift the oldCapacity to the right one bit, which is equivalent to 0 / 2 ^ 1, //oldCapacity > > 2. Move oldCapacity to the right by two bits, which is equivalent to 0 / 2 ^ 2. //Parameter newcapacity = 0; int newCapacity = oldCapacity + (oldCapacity >> 1); //If 0 - 10 < 0 if (newCapacity - minCapacity < 0) // newCapacity = 10; newCapacity = minCapacity; //If 0 - 2147483639 > 0 if (newCapacity - MAX_ARRAY_SIZE > 0) //Is equal to your maximum newCapacity = hugeCapacity(minCapacity); //Copy the elements in the elementData array to the new capacity array, even if it has nothing, so as to complete the capacity expansion //At this time, elementData is 10 zeros; elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { //If the required capacity is less than 0 if (minCapacity < 0) //Throwing anomaly throw new OutOfMemoryError(); //Otherwise, the maximum value between two is returned return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } }
-
Method, add elements -- > to check whether capacity expansion is needed -- > to see the minimum capacity required for this addition -- > if the collection capacity is insufficient, expand the capacity. If that's enough, go back and add. Capacity expansion is to add 1.5 to the current capacity for capacity expansion (except for the elements added for the first time / longer than the collection len gt h). Finally, the old elements are copied to the new array, and the reference remains unchanged.
-
When you add the second element, you have finished judging whether to expand the capacity
//elementData.length = 10ï¼› (because it has been expanded for the first time) //If 2 - 10 > 0, the grow() method is called and the minCapacity value is passed into the method //Now it is not greater than 0. If it is determined that your array capacity is sufficient, you can directly add elements and return if (minCapacity - elementData.length > 0) grow(minCapacity);
The second expansion is triggered by adding the eleventh element, that is, when the index coordinate is 10. Now the index is only 0 to 9. When adding the tenth index element, there is no place to put it, so the second expansion has to be carried out. There will be no demonstration here!
The above ArrayList features have been basically demonstrated, and thread safety and why query speed are not involved. Why its query is fast will be demonstrated together with LinkedList. Now let's talk about why its thread is unsafe!
DemoTest
package com.zhao.Collection; import java.util.ArrayList; import java.util.List; @SuppressWarnings({"all"}) public class ArrayListInThread implements Runnable { //Thread unsafe private List threadList = new ArrayList(); @Override public void run() { try { //Sleep for 10 milliseconds first to avoid running too fast, so that there will be no snatching the execution right of the cup Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //Add the current thread name to the ArrayList (this step is very important, and the error points are here) threadList.add(Thread.currentThread().getName()); } public static void main(String[] args) throws InterruptedException { //Create the current class (because it implements the Runnable interface, I now want to start the thread and call the run method inside it) ArrayListInThread listThread = new ArrayListInThread(); for (int i = 0; i < 100; i++) { //Give each thread a name, starting at 0 Thread thread = new Thread(listThread, String.valueOf(i)); //Open thread thread.start(); } //Wait for the child thread to finish executing Thread.sleep(2000); //Length of output set System.out.println(listThread.threadList.size()); //Output values in ArrayList for (int i = 0; i < listThread.threadList.size(); i++) { //If there is a null value in the collection, it will be output on a new line (the following will explain why null values appear back) if (listThread.threadList.get(i) == null) { System.out.println(); } //Prints each value in the set System.out.print(listThread.threadList.get(i) + " "); } } }
What may happen to the console:
First:
There are only 98 elements in the collection, which should normally be 100. Where are the other two? This is easy to understand. Looking at the source code, we can see that the operation of "elementData[size++] = e" is composed of three steps:
- elementData[size] = e
- size = sizeï¼›// Size indicates the length of the array
- size = size + 1
Thread A | Thread B |
size = 30 | |
elementData[size], add element at | |
size = 30 | |
elementData[size], overwrite element at | |
size = 30; | |
size = 30; | |
size = 30 + 1; | |
size = 30 + 1; | |
size = 31 (came over and made a soy sauce) | size = 31 (all think they added it by themselves) |
The second case:
A null value appears. How does this null value appear?
Thread A | Thread B |
size = 31 | |
elementData[size], add element at | |
size = 31 | |
elementData[size], overwrite element at | |
size = 31; | |
size = 31 + 1; | |
size = 32; | |
size = 32 +1; | |
size = 32 | size = 33 |
There are already elements at index 31, and thread B overwrites and adds them. Thread A thinks it has added them. The set length is + 1, (I don't want to be overwritten by thread b). After thread B completes the coverage, the set length is 32. Because there is no lock, it won't make A judgment similar to optimistic lock, so it will directly + 1 on the set length. As A result, the position of index 32 is not related to the element, resulting in the embarrassment of null value!
The third case:
Familiar exception > index out of bounds. How did this happen?
In fact, it is still related to capacity expansion
Thread A | Thread B |
Add 10th element | Add 10th element |
if (minCapacity - elementData.length > 0) Â Â Â Â Â Â grow(minCapacity); | |
10 - 10 > 0 - false, no capacity expansion | |
Call elementData[size++] = e; Method. Please note that you have just come here and haven't added it yet | |
Go through the process of thread A again and judge that there is no need to expand the capacity | |
elementData[10], add element at | |
size++ï¼›size = 11 | |
elementData[11], add element at | |
Finally, the index is out of bounds! But this does not affect the judgment of the next thread |
It can be seen that ArrayList set merging is not safe, and safe sets are used in multiple concurrent cases. For example, the Vector to be discussed below
                                               Â
                       Â
Vector
Underlying data structure: array
Default initial capacity: 10
Capacity expansion: current array length * 2
Thread safety: Security
Features: it has no advantages except safety
Explain these features from the source code
- DemoTest class
package com.zhao.Collection; import java.util.Vector; public class VectorTest { public static void main(String[] args) { Vector vector = new Vector(); //Create array for (int i = 1; i <= 10; i++) { vector.add(i); //Add data 1 ~ 10 } vector.add(100); //Capacity expansion will be triggered here vector.add(200); } }
- Vector parameterless construction part of the source code
package java.util; import java.io.IOException; import java.io.ObjectInputStream; import java.io.StreamCorruptedException; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ //Empty array (used for initialization) protected Object[] elementData; //Number of elements protected int elementCount; //Self increasing capacity protected int capacityIncrement; //Parametric structure public Vector(int initialCapacity, int capacityIncrement) { super(); //Judge whether the value passed in is less than 0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity:initialCapacityv"); //Initialize the empty array. The default initial capacity is 10 this.elementData = new Object[initialCapacity]; //Assign self increasing capacity this.capacityIncrement = capacityIncrement; } //Parameter structure. The initial capacity is the value passed in by the user public Vector(int initialCapacity) { this(initialCapacity, 0); } //Null parameter structure, the default initial capacity is 10 public Vector() { this(10); } }
- At this time, the collection has been initialized, and the collection length is 0. When the add (integer i) method is executed for the first time, the source code is as follows
package java.util; import java.io.IOException; import java.io.ObjectInputStream; import java.io.StreamCorruptedException; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ //Empty array (used for initialization) protected Object[] elementData; //Number of elements protected int elementCount; //Self increasing capacity protected int capacityIncrement; //Parametric structure public Vector(int initialCapacity, int capacityIncrement) { super(); //Judge whether the value passed in is less than 0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity:initialCapacity"); //Initialize the empty array. The default initial capacity is 10 this.elementData = new Object[initialCapacity]; //Assign self increasing capacity this.capacityIncrement = capacityIncrement; } //Add element public synchronized boolean add(E e) { modCount++; //Modification times //Ensure small capacity assistant //The parameter elementCount defaults to 0, which is an empty collection ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } //Ensure that the capacity must be sufficient. minCapacity:1 private void ensureCapacityHelper(int minCapacity) { //If 1 - 10 > 0 if (minCapacity - elementData.length > 0) //Capacity expansion method grow(minCapacity); } //For the capacity expansion method, minCapacity = 11 when running here for the first time private void grow(int minCapacity) { //The array length is assigned to oldCapacity = 10 int oldCapacity = elementData.length; //capacityIncrement is always 0, which is defined by the Vector designer. 0 > 0 is always true //The new capacity is the sum of the two old capacities int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); //If the newly expanded capacity - minimum required capacity < 0 if (newCapacity - minCapacity < 0) //The minimum required capacity is assigned to the new capacity newCapacity = minCapacity; //If the maximum value range of the newly expanded capacity - Integer is > 0 if (newCapacity - MAX_ARRAY_SIZE > 0) //Assign the maximum capacity to the new capacity newCapacity = hugeCapacity(minCapacity); //Copy all elements from the old array to the new array and point to the old reference elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { //If the minimum required capacity is < 0 if (minCapacity < 0) //Throwing anomaly throw new OutOfMemoryError(); //Returns the maximum of two return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } }
After reading the source code of Vector, I feel that the two sets are not written by the same person. The ArrayList design is a little more rigorous than Vector. But Vector has less code and looks more elegant!
The first expansion of Vector is triggered when the eleventh element is added. Moreover, it uses a condition equal to false to ensure that each expansion is the addition of two old sets. That is, double the capacity.
It is thread safe because its methods are decorated with synchronized. So its performance is also very low. Its thread safety will not be demonstrated here.
LinkedList
Underlying data structure: bidirectional linked list
Default initial capacity: 0
Capacity expansion: no capacity expansion
Thread safe: unsafe
Features: fast addition and deletion, slow query
Next, we explain the above features from the source code
- DemoTest class
package com.zhao.Collection; import java.util.LinkedList; @SuppressWarnings({"all"}) public class LinkedListTest { public static void main(String[] args) { //Create collection LinkedList linkedList = new LinkedList(); for (int i = 1; i <= 100; i++) { //Add element linkedList.add(i); } //modify linkedList.set(70,999); System.out.println("Modified LinkedList: "+linkedList); //query Integer elementBy70 = (Integer) linkedList.get(70); System.out.println("LinkedList Element at index 70: "+elementBy70); //delete linkedList.remove(); System.out.println("After deleting an element LinkedList: "+linkedList); } }
- LinkedList parameterless construction part of the source code
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable,java.io.Serializable{ //The collection length is 0 by default transient int size = 0; //Head node transient Node<E> first; //Tail node transient Node<E> last; //Empty parameter structure public LinkedList() { } //Inner class private static class Node<E> { //Node element (the object you add when you add) E item; //Address of the next node Node<E> next; //Address of the previous node Node<E> prev; //Parameterized construction to assign values Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } }
-
When the add (integer i) method is executed for the first time, the source code is as follows
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable,java.io.Serializable{ //The collection length is 0 by default transient int size = 0; //Head node transient Node<E> first; //Tail node transient Node<E> last; //Add element public boolean add(E e) { //Call the method to add the tail node linkLast(e); return true; } //Tail node append void linkLast(E e) { //At this time, last is uninitialized and null. Assign the null value to the node l final Node<E> l = last; //Create a new Node. The Node has parameters. The three parameters are the previous Node, the added element and the next Node //The previous node of the new node points to l final Node<E> newNode = new Node<>(l, e, null); //Assign the new node to the last variable (record it, which is the head node address of the next node) last = newNode; //If l == null if (l == null) //Assign the new node to the first variable first = newNode; else l.next = newNode; //Set length + 1 size++; //Modification times + 1 modCount++; } //Inner class private static class Node<E> { //Node element (the object you add when you add) E item; //Address of the next node Node<E> next; //Address of the previous node Node<E> prev; //Parameterized construction to assign values Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } }
-
The add method in LikendList is exquisitely designed, just a little around. I suggest you run the source code again, so that you can deeply understand it
The following compares the query and deletion of ArrayList and LinkedList through the source code
ArrayList has fast queries and slow additions and deletions
LinkedList has slow queries and fast additions and deletions
ArrayList query and delete source code
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import sun.misc.SharedSecrets; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ //Element data. Variables modified by the transient keyword are not serialized or deserialized transient Object[] elementData; //Array length private int size; //Query & & get public E get(int index) { //Check index rangeCheck(index); //Returns the element corresponding to the index from the array return elementData(index); } //delete public E remove(int index) { //Check index rangeCheck(index); //Modification times + 1 modCount++; //Gets the corresponding element at index E oldValue = elementData(index); //Array length - index value - 1 to determine whether the last element is deleted int numMoved = size - index - 1; //If it is true, it indicates that the last bit is not deleted, and the element position in the array needs to be readjusted if (numMoved > 0) //Call local methods to adjust the position of elements in the array System.arraycopy(elementData, index+1, elementData, index, numMoved); //Set the position of the last bit in the array length to null elementData[--size] = null; // Let GC do its cleanup //Return old value return oldValue; } //Get element E elementData(int index) { //Gets the element at index return (E) elementData[index]; } //Check whether the index is legal private void rangeCheck(int index) { //If the index is greater than or equal to the array length if (index >= size) //Throw exception throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //Console exception information private String outOfBoundsMsg(int index) { //Printed character information return "Index: "+index+", Size: "+size; } }
It can be seen from the source code that the query speed of Arraylist is really fast. According to the corner mark query, which element you need can be directly located on the required element. But its deletion efficiency is not satisfactory, if it is not to delete the last element. It also needs to call local methods to copy the elements in the array, and then fill in the missing index. Consume more performance!
LinkedList query and delete source code
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable,java.io.Serializable{ //The collection length is 0 by default transient int size = 0; //Head node transient Node<E> first; //Tail node transient Node<E> last; //Get value from index public E get(int index) { //Index check checkElementIndex(index); //Call the node method to get the value of the index position return node(index).item; } //Element at index position Node<E> node(int index) { //If the index is less than half the length of the collection if (index < (size >> 1)) { //Assign the first node address to the variable x Node<E> x = first; //The traversal starts from the first node until the end of the previous element of the index for (int i = 0; i < index; i++) //Assign the address value where the index is located to the x node x = x.next; //Returns the node element corresponding to the index return x; } else { //Assign the last node to the variable x Node<E> x = last; //The traversal starts from the last node until the end of the next element after the index for (int i = size - 1; i > index; i--) //Assign the address value where the index is located to the x node x = x.prev; //Returns the node element corresponding to the index return x; } } //Check index private void checkElementIndex(int index) { //If it is legal and the inverse is false, the method is not entered //Illegal. If it is reversed to true, an exception will be thrown if (!isElementIndex(index)) //Throw index out of bounds exception throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //Determine whether the index is within the range of the set private boolean isElementIndex(int index) { //Returns the Boolean value of index > = 0 & & index < size return index >= 0 && index < size; } //Abnormal information private String outOfBoundsMsg(int index) { //Character information printed by console return "Index: "+index+", Size: "+size; } //Removing Elements public E remove() { //Remove first by default return removeFirst(); } //Remove first element public E removeFirst() { //Assign the header node to the variable f final Node<E> f = first; //If the header node is equal to null if (f == null) //Throw exception throw new NoSuchElementException(); //Call the method to remove the header node return unlinkFirst(f); } //Remove header node private E unlinkFirst(Node<E> f) { //Get the element of the header node final E element = f.item; //Get the next node address of the head node final Node<E> next = f.next; //Set the element of the header node to null f.item = null; //Set the address of the header node pointing to the next node to be empty, and the empty object waits for GC recycling f.next = null; // help GC //Assign the next node address obtained in the previous two steps to the member variable header node first = next; //If the next node is empty (there is only one element in the collection, that is, the element just deleted) if (next == null) //Set the member variable last to null last = null; else //Set the address of the next node of the current header node to null (wait for the next added node to complete it) next.prev = null; //Set length minus 1 size--; //Modification times + 1 modCount++; //Returns the deleted header node return element; } //Removes the element at the specified index public E remove(int index) { //Check index checkElementIndex(index); //Remove the node. The node(index) method obtains the corresponding node at the index first return unlink(node(index)); } //Remove node E unlink(Node<E> x) { //Gets the element of the incoming node final E element = x.item; //Get the address value of the next node final Node<E> next = x.next; //Get the address value of the previous node final Node<E> prev = x.prev; //If the previous address value is null, (the first node is deleted) if (prev == null) { //Make the next node the first node. The prev of the next node will disappear with the GC cleared first = next; } else { //Connect the address value of the next node of the element to be deleted with the next attribute of the previous node prev.next = next; //After the handover is ready, set the address value of the last node of the deleted element to null x.prev = null; } //If the address value of the next node of the deleted node is null, the last element if (next == null) { //Set the address value of the previous node to the last element last = prev; } else { //Connect the address value of the previous node of the element to be deleted with the prev attribute of the next node next.prev = prev; //After the handover is ready, set the address value of the next node of the deleted element to null x.next = null; } //Set element to null x.item = null; //Set length-- size--; //Modification times++ modCount++; //Returns the deleted element return element; } //Inner class private static class Node<E> { //Node element (the object you add when you add) E item; //Address of the next node Node<E> next; //Address of the previous node Node<E> prev; //Parameterized construction to assign values Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } }
It can be seen from the source code that the addition and deletion of LinkedList is fast as long as the index is not specified. It does not need to copy elements like ArrayList and add them to the collection again. Saves copy overhead. But its query speed is really moving. Although the dichotomy is used for optimization in the source code, it still needs to traverse to the index position one by one. Then take out the elements in the node.
In view of the nodes around in the LinkedList source code, in the future, as long as the array does not involve header insertion, choose ArrayList. Vector can be selected for thread safety. LinkedList thread is not safe. Vector is still used for thread safety, although it is not recommended in JDK.