Let me first talk about my own situation: I can still solve the bugs encountered in the Java project in front of the video, but EVERY time I finish a project, I feel very empty. I feel that I understand the knowledge points in the project, but I don’t seem to understand them. How can I master the knowledge points used in a project? At least not after half a month without being able to remember what the project was.

Write a blog? Mind mapping? Or what? Do you have any experience that you can give us?

First of all, try to analyze the reasons why the subject feels empty and confused. From the description of the question, the reasons may be as follows:

Unclear goals

Before learning the project, have you carefully sorted out and thought about what technologies you hope to learn and what key knowledge points you need to master through the project? These knowledge points belong to their own technical system in which link, is it necessary to master or understand the principle can? I believe that only after a clear goal can there be a focus and direction of learning.

Learning methods

Are there questions and thoughts during the project learning process? For example, the core problem scenarios need to be solved, which technical solutions are used, why are these technologies needed, and what are the main considerations for the solution selection? What are the benefits of this layering and implementation of system modules? The implementation of this method, whether the performance can be further optimized, etc.

If I just follow the video and mechanically type the project code, I don’t think it is any different from practicing typing, and the written code is also soulless. I believe that only by combining my own thinking and understanding, can I endow myself with a new soul. Only by knowing what it is and why it is, can the relevant knowledge points be truly transformed into my own technology.

Review and Application

Paper come zhongjue shallow, must know this matter to practice, I believe that programming is even more so, only practice can be true knowledge. The relevant technologies and knowledge points learned in the project need to be repeatedly practiced and applied in different scenarios, and the problems encountered in the process should be constantly summarized and reflected.

Second, back to the main question, how do you understand a Java project? From personal experience, we can start from the following aspects:

Project Background

Before learning, it is very important to have a general understanding of the project business background and technical system. One is to understand the core problem domain to be solved in the project, and the other is to know which technical system is involved in the system. In this way, relevant technical knowledge can be prepared before learning, so that it is easier and efficient to learn. In addition, after learning, you can clearly know what kind of problems can be solved by what technologies, what solutions and how to solve them.

System design documentation study

After a general understanding of the project and the system, you can start to get familiar with the system design documents. It is recommended to proceed according to the architecture documents, outline design and detailed design. Through the study of design documents, we can quickly have a framework understanding of each system module, know what each system responsibility, boundary, how to interact, system core model and so on.

For the study of design documents, must not fly-by-night, must bring questions and thinking. For example, the core business issues in the project context, how the architect translated into technology implementation, why the solution was designed this way, why the model was abstract this way, what are the benefits of doing so, and so on? At the same time, take good notes on the questions you don’t understand, so that you can consult or discuss with teachers or other colleagues later.

System knowledge and code reading

After learning the design documents and having an overall understanding of the system design, you can then look at how the code is implemented in combination with business scenarios and related problems. However, the code reading, need to pay attention to the method, must not get into the details of the code, should be top-down, hierarchical module reading, in the first whole, after the module, single function point of the way step by step. Quickly walk through the entire code module logic, and then read the implementation of a class or method.

During the code reading process, it is recommended to sort out relevant code modules, process branches, interaction sequences, and class diagrams while reading for better understanding. Some IDE tools can also be generated automatically based on the code, such as IntelliJ IDEA.

Code reading not only focuses on the implementation of specific functions, but also needs to be concerned about the ideas and principles of code design, performance considerations, design patterns, and the application of design principles. It is also important to read code comments. When researching an API or method implementation, reading code comments first will help you get the most out of it. Try not to extrapolate logic and functionality from the code.

Finally, for the core function code recommended module intensive reading, do not understand the part can use code debugging.

Then, I would like to give some personal suggestions for the subject’s reference:

Make a Study plan

Sort out a suitable technical plan, and formulate a clear learning route and plan, so that learning more oriented and focused. Also, I will be more clear in choosing video courses, and I will know what videos to learn and what not to learn, and I will not easily feel confused and empty. With so many learning materials and videos available online, it’s important to learn how to sift through information that is effective and appropriate for you.

Thinking and Practice

There are no shortcuts to technical programming. Thinking and practice are both very important, and it requires constant learning, thinking, and practice. From understanding, will use, know the principle, optimization evolution. Combined with the learning plan, you can set yourself different challenges, such as learning Spring and trying to implement an IOC container by yourself. In addition, problems encountered in the course of work or study are also a good way to improve your technical ability quickly, and please cherish every opportunity you encounter problems. When you have time, help others with their questions as well. Stackoverflow is a great way to help others and improve yourself at the same time.

Sharing and communication

Keep the habit of thinking and summarizing, and share the techniques learned with others. Work with good programmers, work on good open source projects, etc.

Finally, I will explain how to learn and quickly master a project with the example of TransmittableThreadLocal(TTL), another open source framework of our Dubbo core developer @transmittableThreadLocal (TTL).

Combined with the above, I will carefully read the documents related to the TTL project and issues list, so that I can have a general understanding of the project and sort out some key information of the project, such as:

• Core problems to be solved

• To solve “how to solve the ThreadLocal value transfer problem in the case of thread pool or thread reuse”

• Typical business scenarios

• Distributed tracking system or full link pressure measurement (i.e. link marking)

• Log collection Records the system context

Level, the Session Cache

• The application container or the upper framework passes information to the lower SDK across the application code

• The technology used

• There are threads, thread pools, ThreadLocal, InheritableThreadLocal, concurrency, thread safety, etc.

Then, I wrote several test demos in combination with the documentation, and deepened my understanding of the framework step by step through code practice and framework use. For example, I will first compare TTL with InheritableThreadLocal in the native JDK to experience the core differences between the two.

public class ThreadLocalTest { private static final AtomicInteger ID_SEQ = new AtomicInteger(); private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1, r -> new Thread(r, "TTL-TEST-" + ID_SEQ.getAndIncrement())); // private static ThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal<>(); // Private static ThreadLocal<String> THREAD_LOCAL = new TransmittableThreadLocal<>(); public static void testThreadLocal() throws InterruptedException { try { //doSomething()... THREAD_LOCAL.set("set-task-init-value"); // Runnable task1 = () -> { try { String manTaskCtx = THREAD_LOCAL.get(); System.out.println("task1:" + Thread.currentThread() + ", get ctx:" + manTaskCtx); THREAD_LOCAL.set("task1-set-value"); } finally { THREAD_LOCAL.remove(); }}; EXECUTOR.submit(task1); //doSomething.... TimeUnit.SECONDS.sleep(3); //⑵ Set the context thread_local. set("main-task-value") that is expected to be available to task2; Runnable task2 = () -> {String manTaskCtx = thread_local.get (); System.out.println("task2:" + Thread.currentThread() + ", get ctx :" + manTaskCtx); }; //task2 = TtlRunnable. Get (task2); EXECUTOR.submit(task2); }finally { THREAD_LOCAL.remove(); } } public static void main(String[] args) throws InterruptedException { testThreadLocal(); }} //InheritableThreadLocal Task1: Thread [TTL - TEST - 0, 5, the main], get CTX: set - a task - init - value task2: Thread/TTL - TEST - 0, 5, the main, The get CTX: null / / TransmittableThreadLocal run results task1: Thread/TTL - TEST - 0, 5, the main, Get CTX :set-task-init-value task2:Thread[ttL-test-0,5,main], get CTX :main-task-valueCopy the code

If there is any content with InheritableThreadLocal (if there is any content with TransmittableThreadLocal), we cannot obtain the context parameters that are set within the task2. If there is any content with TransmittableThreadLocal, we cannot obtain the context parameters that are set within the task2. The program was acquired as we expected.

If the JDK native ThreadLocal is changed to TransmittableThreadLocal, minimal code adaptation is required.

//private static ThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal<>(); //⑴ Declare the TransmittableThreadLocal private static ThreadLocal<String> THREAD_LOCAL = new TransmittableThreadLocal<>(); . Task2 = TtlRunnable. Get (task2); //⑷ (4) if there is any problem with TransmittableThreadLocal, task2 = TtlRunnable.Copy the code

How can context passthrough be smoothed out with a simple change of two lines of code? What is done behind the TTL framework and how is it implemented? Like me, you’ll be curious and want to read the source code right away.

However, usually this time, I will not be a head into the source, generally do a few project preparation work, one is to return to the design document carefully read the relevant implementation scheme, the key process and principle understand clearly; Two is the technical body related to the basic knowledge of review or study again, in order to avoid some basic knowledge of the principle of ignorance, resulting in the source can not be in-depth study or spend a lot of energy. If I am not familiar with Thread, ThreadLocal, InheritableThreadLocal, Thread pool, etc., I will learn the relevant knowledge first. Examples include ThreadLocal fundamentals, underlying data structures, how InheritableThreadLocal implements parent-child thread passing, and more.

Assuming that you have mastered the knowledge here, if you are not familiar with it, the online related articles have already been packed with sweat, you can search and learn. Here we first with exactly how to achieve this question, together to explore the core source code implementation.

First of all, the source code clone down into IDE, and then combined with the documentation of the system engineering structure and the responsibility of each function module quickly familiar with a time, and then combined with the documentation and Demo to find the key interface and implementation class, using IDE to generate the relevant class diagram structure, in order to quickly understand the relationship between classes. Very good, TTL overall code is very concise, naming and package information description is very standard and clear, we can quickly circle.

The core key class TransmittableThreadLocal inherits from ThreadLocal without destroying the native capabilities of ThreadLocal. The core key class TransmittableThreadLocal has the advantage of enhancing and extending its own capabilities without destroying the native capabilities of ThreadLocal and ensuring the original interoperability and minimal changes of business code.

Then, with the Demo code, we use TTL with three steps: declaration of TransmittableThreadLocal, call of set and remove methods. Based on the overall usage flow and method call stack, we can easily tease out the entire code handling initialization and call timing.

(Here is the official version)

Through the flow chart, we can clearly see the TTL core processes and principles is through TransmittableThreadLocal Transmitter grab all the TTL value and the current thread in other threads for playback, then in the replay after threads to execute the business operation, return to playback thread TTL value of the original.

TransmittableThreadLocal. Transmitter provides all the TTL value capture, replay and recovery method (CRR) : the capture method: grab thread thread (A) all the TTL value. Replay: in another thread (thread B), replay the TTL captured in the capture method and return a backup of the TTL value before replay.

When the TransmittableThreadLocal variable is declared, the framework initializes a class-level variable holder that stores all TTL contexts set by the user. This is also used for capture capture.

// Note about the holder: // 1. holder self is a InheritableThreadLocal(a *ThreadLocal*). // 2. The type of value in the holder is WeakHashMap<TransmittableThreadLocal<Object>, ? >. // 2.1 But the WeakHashMap is used as a *Set*: // the value of WeakHashMap is *always* null, // 2.2 WeakHashMap Support * NULL * value. Private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ? >> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ? >>() { @Override protected WeakHashMap<TransmittableThreadLocal<Object>, ? > initialValue() { return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(); } @Override protected WeakHashMap<TransmittableThreadLocal<Object>, ? > childValue(WeakHashMap<TransmittableThreadLocal<Object>, ? > parentValue) { return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue); }}; /** * see {@link InheritableThreadLocal#set} */ @Override public final void set(T value) { if (! disableIgnoreNullValueSemantics && null == value) { // may set null to remove value remove(); } else { super.set(value); addThisToHolder(); } } private void addThisToHolder() { if (! holder.get().containsKey(this)) { holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value. } }Copy the code

Super.set (value)); super.set(value); Capture’s data source uses holder to store references (addThisToHolder put this). This preserves the original encapsulation nature of ThreadLocal data stores while allowing for easy scaling. In addition, Holder has other elegant design, here you can think about:

  1. Why does holder need to be designed as a static final class level variable?

  2. Why do I need to use WeakHashMap instead of HashMap or something else to store TTL variables?

TtlRunnable. Get (Task2) is a static factory method that decorates a normal Runnable task as a TtlRunable class. The thread capture action is performed in the TtlRunable constructor (more on the implementation later) and the result is stored in the object property capturedRef.

@Nullable public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (null == runnable) return null; if (runnable instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlRunnable) runnable; else throw new IllegalStateException("Already TtlRunnable!" ); } / / will be into the runnable refs decoration return new TtlRunnable (runnable, releaseTtlValueReferenceAfterRun); } / /... public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments { private final AtomicReference<Object> capturedRef; private final Runnable runnable; private final boolean releaseTtlValueReferenceAfterRun; private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference<Object>(capture()); this.runnable = runnable; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } /** * wrap method {@link Runnable#run()}. */ @Override public void run() { final Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && ! capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!" ); } final Object backup = replay(captured); try { runnable.run(); } finally { restore(backup); }} / /... }Copy the code

Then there’s the run method, which is the core CRR operation. That’s what’s expected of the business logic. That’s what’s expected of the business logic. That’s what’s expected of the business logic. There are also a few questions worth thinking about:

  1. Why does the capture operation need to be in the TtlRunnable constructor and not in the Run method?

  2. What are the two design patterns used in the code, and what are the benefits of using them?

  3. Why do I need a restore operation after the business has been executed?

Next, we’ll look at the capture, replay, and Restore method implementations one by one. Capture is a simple operation. The value of the set operation is stored in the holder variable, traversed, and returned with the Snapshot structure.

/** * Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread. * * @return the captured {@link TransmittableThreadLocal} values * @since 2.3.0 */ @nonnull Public Static Object Capture () {  return new Snapshot(captureTtlValues(), captureThreadLocalValues()); } private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() { HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>(); for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) { ttl2Value.put(threadLocal, threadLocal.copyValue()); } return ttl2Value; } private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() { final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>(); for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) { final ThreadLocal<Object> threadLocal = entry.getKey(); final TtlCopier<Object> copier = entry.getValue(); threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get())); } return threadLocal2Value; }Copy the code

The other captureThreadLocalValues is used to copy together some context from existing ThreadLocal that needs to be registered separately through the registerThreadLocal method. The relevant codes are as follows:

public static class Transmitter { //.... private static volatile WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> threadLocalHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>(); private static final Object threadLocalHolderUpdateLock = new Object(); / /... public static <T> boolean registerThreadLocal(@NonNull ThreadLocal<T> threadLocal, @NonNull TtlCopier<T> copier, boolean force) { if (threadLocal instanceof TransmittableThreadLocal) { logger.warning("register a TransmittableThreadLocal instance, this is unnecessary!" ); return true; } synchronized (threadLocalHolderUpdateLock) { if (! force && threadLocalHolder.containsKey(threadLocal)) return false; WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> newHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>(threadLocalHolder); newHolder.put((ThreadLocal<Object>) threadLocal, (TtlCopier<Object>) copier); threadLocalHolder = newHolder; return true; }} / /... }Copy the code

There is a very critical treatment in the code here, because WeakHashMap is not thread-safe, and synchronized locking is added to avoid concurrency issues. Here’s a way to think about thread safety other than the synchronized keyword. In addition, threadLocal is already registered in the lock block, so why do we need to do a new copy re-replace operation? You can think about it.

Finally, there are the replay and restore methods. The logic is pretty clear, reassigning and initializing captured values in the current ThreadLocal and restoring them after they’ve been captured. The set context of capture is removed from the set context of capture. The set context of capture is removed from the set context of capture. The set context is removed from the set context of capture.

@NonNull public static Object replay(@NonNull Object captured) { final Snapshot capturedSnapshot = (Snapshot) captured; return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value)); } @NonNull private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) { HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>(); for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal<Object> threadLocal = iterator.next(); // backup backup.put(threadLocal, threadLocal.get()); // clear the TTL values that is not in captured // avoid the extra TTL values after replay when run task if (! captured.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // set TTL values to captured setTtlValuesTo(captured); // call beforeExecute callback doExecuteCallback(true); return backup; } private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> ttlValues) { for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) { TransmittableThreadLocal<Object> threadLocal = entry.getKey(); threadLocal.set(entry.getValue()); } } public static void restore(@NonNull Object backup) { final Snapshot backupSnapshot = (Snapshot) backup; restoreTtlValues(backupSnapshot.ttl2Value); restoreThreadLocalValues(backupSnapshot.threadLocal2Value); } private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> backup) { // call afterExecute callback doExecuteCallback(false); for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal<Object> threadLocal = iterator.next(); // clear the TTL values that is not in backup // avoid the extra TTL values after restore if (! backup.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // restore TTL values setTtlValuesTo(backup); }Copy the code

After the core code analysis, let’s briefly summarize the knowledge points learned in the project:

  1. I have a more systematic and in-depth understanding of ThreadLocal and InheritableThreadLocal, including the inheritance relationship between the two and the association relationship between the underlying data structure ThreadLocalMap and Thread, etc.

  2. Gc oriented programming (GC dependent), WeakHashMap(Java object reference types strong, soft, weak, etc.), thread safety, concurrency, etc

  3. Design patterns related, decorative patterns, factories, template methods, agents, etc

  4. Although TTL code quantity is not much, it is short and concise, which also reflects the author’s super high design and programming ability everywhere. Every line of code is worth learning and pondering over.

I believe that through the learning process of a project like this, I can do a solid job in every link and have my own thinking and understanding throughout the process. I believe that you will be able to understand each project thoroughly, and each technical point in the project is firmly mastered.

Finally, MY team is the architecture team of Tao Department technology Department, mainly responsible for the construction of one-stop SERVERless R&D platform, to continuously improve r&d efficiency and ultimate experience for the business. The platform has steadily supported Amoy interactive, Taobao life, gold manor, special edition, idle fish, auction, brand light shop and other businesses of 6.18, double 11, double 12, Spring Festival Gala and other big promotion activities.


Welcome to join Amoy architecture team. The team has a large number of members, including founders of Ali Mobile Middleware, core members of Dubbo, and a group of partners who love technology and hope to promote business with technology.

Taoshi Architecture team promotes the architecture upgrade of Taoshi (Taobao, Tmall, etc.), and is committed to providing basic core capabilities, products and solutions for Taoshi and the whole group:

• Highly available service solutions and core capabilities (Refined traffic control) Marconi platform: provides adaptive flow control, isolation, and fusing flexible high availability solutions for services. High availability of sites: self-healing of faults, multi-room and remote disaster recovery, and fast cut-flow recovery

• GAIA, a one-stop SERVERless R&D platform, provides business with efficient R&D efficiency and ultimate experience.

• Implementation and implementation of the next generation network protocol QUIC

• Mobile middleware (API gateway MTop, access layer AServer, messaging/push, configuration center, etc.)

Looking forward to joining the basic platform construction of Tao department ~

Resume should be sent to 📮 : shao Qian [email protected]