That's a huge topic but I would say implement a hobby project and learn by doing. Pick something you're interested in and start writing code to exercise the theoretical concepts.
A small piece of advice is to make sure you're motivated before diving in. Debugging a race condition, just as an example, can be quite involved and consume a lot of your time and energy to even reproduce.
I started by writing a user interface that handled they keyboard events in a thread and communicated to the main thread using a message queue.
IMO that's a good easy first step :)
Java Concurrency in Practice[1] has always been recommended by my colleagues. I'm about halfway through it and I think it makes the concepts pretty clear. Even if you move away from Java one day, I think the investment is not lost at all. Then you could ask your favorite LLM to create concurrency exercises once in a while to practice.
1) Foundations of Multithreaded, Parallel, and Distributed Programming by Gregory Andrews - https://www2.cs.arizona.edu/~greg/mpdbook/ This will give you a solid foundation in all aspects of concurrency.
I read the “The Art of Multiprocessor Programming” and I don’t recommend it. It is very theoretical. There is no mention of practical performance considerations on real hardware.
Large parts of the theory focus on lock-free and wait-free data structures. Which, while interesting, are not necessary for beginners.
If you don’t mind learning another language. I have found Learn Concurrent Programming with Go by James Cutajar to be a very practical book. It includes memory sharing and message passing approaches with plenty of examples. It also explains concepts like mutual exclusion, deadlock-free and starvation-free properties and others. For Java, you can try The Art of Multiprocessor Programming Second Edition. It includes examples in Java but it is more theoretical and it includes a lot of proofs, specially the first half. The second half is more approachable.
Docs of java.util.concurrent would be my suggestion. And go straight into the wild and try to build a system that solves the billion rows challenge. You might want to
truncate the input and start with a million row challenge first.
The cool stuff in concurrency is not having to deal with it imoand recognizing when its not essential. Also I hope you mean concurrency not parallelism. The second one is a bit more manageable.
People in the comments wish to provide assistance and share their experience, clearly missing the fact that the person asking didn't even bother to do a simple search first.
A small piece of advice is to make sure you're motivated before diving in. Debugging a race condition, just as an example, can be quite involved and consume a lot of your time and energy to even reproduce.