JDK HashSet is built on top of a HashMap<T, Object>, where value is a singleton ‘present’ object. It means that the memory consumption of a HashSet is identical to HashMap: in order to store SIZE values, you need 32 * SIZE + 4 * CAPACITY bytes (plus size of your values). 
For ArrayList, it's the capacity of the java.util.ArrayList multiplied by the reference size (4 bytes on 32bit, 8bytes on 64bit) + [Object header + one int and one references].
So HashSet is definitely not a memory-friendly collection.
Depends if you're using a 32-bit or a 64-bit VM. That said, HashSet gets hurt worse by 8-byte references than ArrayList does -- adding an extra 4 bytes per reference, based on the linked memory consumption chart, brings ArrayList up to ~12 bytes per element, and HashSet up to ~52 bytes per element.)
ArrayList is implemented using an array of Objects. Figure below shows the memory usage and layout of an ArrayList on a 32-bit Java runtime:
Memory usage and layout of a ArrayList on a 32-bit Java runtime

The figure above shows that when an ArrayList is created, the result is an ArrayList object using 32 bytes of memory, along with an Object array at a default size of 10, totaling 88 bytes of memory for an empty ArrayList. This means that the ArrayList is not accurately sized and therefore has a default capacity, which happens to be 10 entries.
Attributes of an ArrayList
Default capacity -    10
Empty size      -    88 bytes
Overhead  -    48 bytes plus 4 bytes per entry
Overhead for 10K collection   - ~40K
Search/insert/delete performance- O(n) — Time taken is linearly dependent to the number of elements
A HashSet has fewer capabilities than a HashMap in that it cannot contain more than one null entry and cannot have duplicate entries. The implementation is a wrapper around a HashMap, with the HashSet object managing what is allowed to be put into the HashMap object. The additional function of restricting the capabilities of a HashMap means that HashSets have a slightly higher memory overhead.
Memory usage and layout of a HashSet on a 32-bit Java runtime

The figure above shows the shallow heap (memory usage of the individual object) in bytes, along with the retained heap (memory usage of the individual object and its child objects) in bytes for a java.util.HashSet object. The shallow heap size is 16 bytes, and the retained heap size is 144 bytes. When a HashSet is created, its default capacity — the number of entries that can be put into the set — is 16 entries. When a HashSet is created at the default capacity and no entries are put into the set, it occupies 144 bytes. This is an extra 16 bytes over the memory usage of a HashMap. 
Table below shows the attributes of a HashSet:
Attributes of a HashSet
Default capacity  -16 entries
Empty size    -144 bytes
Overhead  -16 bytes plus HashMap overhead
Overhead for a 10K collection -16 bytes plus HashMap overhead
Search/insert/delete performance  - O(1) — 
Time taken is constant time, regardless of the number of elements 
(assuming no hash collisions)
     
    
init()method be in synchronized block?isValidString()should have a synchronized block which ensuresinit();is called only once. Also you need to check fornulltwice.inValidString()synchronized it defeats the whole purpose of multithreading, you should only make theif(stringList==null)condition synchronized.stringListanyway? You've got to load the collection before it makes sense to query it, so you're not going to get any parallelism there. So why not initialize in the constructor?