Thread safety is not about threads being safe, it's about objects being safe from being modified by multiple threads at the same time.
Without proper synchronization, if two different threads access the same object this may lead to the object ending up in an invalid state. For example, the body of ArrayList.add:
Let's focus on that "elementData[size++] = e" line. That consists out of three operations:
1) get the current value for size
2) increment size
3) set the element at the specified index (old value of size)
Since the method is not synchronized, two threads X and Y can have any interweaving of these statements. Ideally, first X executes 1-3 and then Y executes 1-3. But what if the following occurs (assumption: size is 5 when called)
X.1 (current value is 5)
Y.1 (current value is 5)
X.2 (size is 6)
X.3 (element 5 is set)
Y.3 (size is 7)
Y.3 (element 5! is set)
So the size is increased by 2 but only one element is effectively added; the addition of thread X is overwritten by the addition of thread Y.
Now, back to Swing / AWT and the EDT. All user interface related code, both querying and modifying, should be done on the EDT. EventQueue.invokeLater, EventQueue.invokeAndWait (with "aliases" SwingUtilities.invokeLater and SwingUtilities.invokeAndWait) and SwingWorker's process and done methods are ways to execute code on the EDT from another thread (SwingWorker's execute method starts a new thread).
You should definitely use these when using multiple threads and Swing / AWT.