Thread-safe LRU Cache Implementation in Java
I like the codestyle!
Code organization tips:

Its better to move DDLNode into LRU and make it static, because sole purpose of this class is to be used by LRU class.

If you are not going to expose DDLNode to outside world, then it make sense to make it also private. Keeping its fields as public is fine here.

If you want to expose DDLNode to outside world (for example, in iterator), you should think more thoroughly about field access. You can get inspiration from HashMap.Node class, for example: fields here have default access, so HashMap itself can manipulate them without using getter or setters. Also note, that there is no setKey() method for outside world, because if we change the key on the node it will break the map.

Maybe you just put all the classes into 1 file to make it easies for us to read , but if not - its bad practice to have more than 1 class/interface in 1 file.


Optimization tips:

In get() you are doing unnecessary .containsKey() call, because just .get(key) is enough: if key if present, it will find it; if not, then it will return null and you can then just return from method.

Unnecessary containsKey() call in put() as well: you can just check .get(key) == null instead.

head and tail will always occupy memory, event if map is empty. They could be removed, although then you should be more careful will null checks.