From 7ba4fd3824315609c26f519c80efe62c41b61d49 Mon Sep 17 00:00:00 2001
From: wupeixuan <18603203910@163.com>
Date: Sun, 5 Apr 2020 15:18:30 +0800
Subject: [PATCH 1/6] =?UTF-8?q?ThreadLocal=20=E6=BA=90=E7=A0=81=E8=A7=A3?=
=?UTF-8?q?=E6=9E=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 6 +-
src/java/lang/InheritableThreadLocal.java | 62 +----
src/java/lang/ThreadLocal.java | 317 ++++++++++------------
src/java/lang/ref/WeakReference.java | 60 +---
4 files changed, 164 insertions(+), 281 deletions(-)
diff --git a/README.md b/README.md
index 1b5a971e..688e0801 100644
--- a/README.md
+++ b/README.md
@@ -40,4 +40,8 @@
Runtime 源码解析
-ThreadLocal 源码
\ No newline at end of file
+ThreadLocal 源码解析
+
+InheritableThreadLocal 源码解析
+
+WeakReference 源码解析
\ No newline at end of file
diff --git a/src/java/lang/InheritableThreadLocal.java b/src/java/lang/InheritableThreadLocal.java
index 9d75035f..c12b8ff1 100644
--- a/src/java/lang/InheritableThreadLocal.java
+++ b/src/java/lang/InheritableThreadLocal.java
@@ -1,59 +1,13 @@
-/*
- * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
package java.lang;
-import java.lang.ref.*;
/**
- * This class extends ThreadLocal to provide inheritance of values
- * from parent thread to child thread: when a child thread is created, the
- * child receives initial values for all inheritable thread-local variables
- * for which the parent has values. Normally the child's values will be
- * identical to the parent's; however, the child's value can be made an
- * arbitrary function of the parent's by overriding the childValue
- * method in this class.
- *
- *
Inheritable thread-local variables are used in preference to
- * ordinary thread-local variables when the per-thread-attribute being
- * maintained in the variable (e.g., User ID, Transaction ID) must be
- * automatically transmitted to any child threads that are created.
+ * 创建允许子线程继承的 ThreadLocal
*
- * @author Josh Bloch and Doug Lea
- * @see ThreadLocal
- * @since 1.2
+ * @see ThreadLocal
*/
-
public class InheritableThreadLocal extends ThreadLocal {
/**
- * Computes the child's initial value for this inheritable thread-local
- * variable as a function of the parent's value at the time the child
- * thread is created. This method is called from within the parent
- * thread before the child is started.
- *
- * This method merely returns its input argument, and should be overridden
- * if a different behavior is desired.
+ * 拿到父线程的值后,可以在这里处理后再返回给子线程
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
@@ -63,18 +17,18 @@ protected T childValue(T parentValue) {
}
/**
- * Get the map associated with a ThreadLocal.
+ * 获取当前线程内的 inheritableThreadLocals 属性
*
- * @param t the current thread
+ * @param t 当前线程
*/
ThreadLocalMap getMap(Thread t) {
- return t.inheritableThreadLocals;
+ return t.inheritableThreadLocals;
}
/**
- * Create the map associated with a ThreadLocal.
+ * 初始化线程中的 inheritableThreadLocals 属性
*
- * @param t the current thread
+ * @param t 当前线程
* @param firstValue value for the initial entry of the table.
*/
void createMap(Thread t, T firstValue) {
diff --git a/src/java/lang/ThreadLocal.java b/src/java/lang/ThreadLocal.java
index 43044fb3..72610902 100644
--- a/src/java/lang/ThreadLocal.java
+++ b/src/java/lang/ThreadLocal.java
@@ -1,102 +1,63 @@
package java.lang;
+
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
/**
- * This class provides thread-local variables. These variables differ from
- * their normal counterparts in that each thread that accesses one (via its
- * {@code get} or {@code set} method) has its own, independently initialized
- * copy of the variable. {@code ThreadLocal} instances are typically private
- * static fields in classes that wish to associate state with a thread (e.g.,
- * a user ID or Transaction ID).
- *
- *
For example, the class below generates unique identifiers local to each
- * thread.
- * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
- * and remains unchanged on subsequent calls.
- *
- * import java.util.concurrent.atomic.AtomicInteger;
+ * 线程局部缓存:为线程缓存数据,数据独享
+ * 原理:
+ * 1. 每个线程由一个 ThreadLocalMap 属性,本质就是一个 map
+ * 2. map 里面存储的 称为键值对,存储键值对时需要先求取哈希值
+ * 3. map 里存储的 key 是一个弱引用,其包装了当前线程中构造的 ThreadLocal 对象
+ * 这意味着,只要 ThreadLocal 对象丢掉了强引用,那么在下次 GC 后,map 中的 ThreadLocal 对象也会被清除
+ * 对于那些 ThreadLocal 对象为空的 map 元素,会当为垃圾,稍后会被主动清理
+ * 4. map 里存储的 value 就是缓存到当前线程的值,这个 value 没有弱引用去包装,需要专门的释放策略
+ * 5. 一个线程对应多个 ThreadLocal,一个 ThreadLocal 只对应一个值
*
- * public class ThreadId {
- * // Atomic integer containing the next thread ID to be assigned
- * private static final AtomicInteger nextId = new AtomicInteger(0);
+ * 哈希值碰撞的问题:
+ * 如果是单线程,因为魔数 HASH_INCREMENT 的存在,且不断扩容,这里不容易出现碰撞
+ * 但如果是多线程,哈希值就很容易出现碰撞,因为属性 nextHashCode 是各线程共享的,会导致生成的哈希值出现重复
*
- * // Thread local variable containing each thread's ID
- * private static final ThreadLocal<Integer> threadId =
- * new ThreadLocal<Integer>() {
- * @Override protected Integer initialValue() {
- * return nextId.getAndIncrement();
- * }
- * };
+ * ThreadLocal value = new ThreadLocal<>();
+ * 形成 map 的键值对,value 作为 ThreadLocalMap 中的键,用它来查找匹配的值。
*
- * // Returns the current thread's unique ID, assigning it if necessary
- * public static int get() {
- * return threadId.get();
- * }
- * }
- *
- * Each thread holds an implicit reference to its copy of a thread-local
- * variable as long as the thread is alive and the {@code ThreadLocal}
- * instance is accessible; after a thread goes away, all of its copies of
- * thread-local instances are subject to garbage collection (unless other
- * references to these copies exist).
- *
- * @author Josh Bloch and Doug Lea
- * @since 1.2
+ * @param
*/
public class ThreadLocal {
+
/**
- * ThreadLocals rely on per-thread linear-probe hash maps attached
- * to each thread (Thread.threadLocals and
- * inheritableThreadLocals). The ThreadLocal objects act as keys,
- * searched via threadLocalHashCode. This is a custom hash code
- * (useful only within ThreadLocalMaps) that eliminates collisions
- * in the common case where consecutively constructed ThreadLocals
- * are used by the same threads, while remaining well-behaved in
- * less common cases.
+ * 当前 ThreadLocal 的 hashCode,由 nextHashCode() 计算而来,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置
+ * 一个线程可以有多个 ThreadLocal 实例,各实例之内的原始 hashCode 不相同
+ * 一个 ThreadLocal 实例也可被多个线程共享,此时多个线程内看到的原始 hashCode 是相同的
*/
private final int threadLocalHashCode = nextHashCode();
/**
- * The next hash code to be given out. Updated atomically. Starts at
- * zero.
+ * static + AtomicInteger 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的
+ * 被 static 修饰非常关键,因为一个线程在处理业务的过程中,ThreadLocalMap 是会被 set 多个 ThreadLocal 的,多个 ThreadLocal 就依靠 threadLocalHashCode 进行区分
+ * 所有 ThreadLocal 共享,但每次构造一个 ThreadLocal 实例,其值都会更新
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
- * The difference between successively generated hash codes - turns
- * implicit sequential thread-local IDs into near-optimally spread
- * multiplicative hash values for power-of-two-sized tables.
+ * HASH_INCREMENT 是一个特殊哈希魔数,这主要与斐波那契散列法以及黄金分割有关
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
- * Returns the next hash code.
+ * 返回计算出的下一个哈希值,其值为 i * HASH_INCREMENT,其中 i 代表调用次数
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
- * Returns the current thread's "initial value" for this
- * thread-local variable. This method will be invoked the first
- * time a thread accesses the variable with the {@link #get}
- * method, unless the thread previously invoked the {@link #set}
- * method, in which case the {@code initialValue} method will not
- * be invoked for the thread. Normally, this method is invoked at
- * most once per thread, but it may be invoked again in case of
- * subsequent invocations of {@link #remove} followed by {@link #get}.
+ * 为 ThreadLocal 对象设置关联的初值,具体逻辑可由子类实现
*
- * This implementation simply returns {@code null}; if the
- * programmer desires thread-local variables to have an initial
- * value other than {@code null}, {@code ThreadLocal} must be
- * subclassed, and this method overridden. Typically, an
- * anonymous inner class will be used.
- *
- * @return the initial value for this thread-local
+ * @return 该线程关联的初始值
*/
protected T initialValue() {
return null;
@@ -117,103 +78,103 @@ public static ThreadLocal withInitial(Supplier extends S> supplier) {
}
/**
- * Creates a thread local variable.
+ * 构造 ThreadLocal 实例
+ *
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
/**
- * Returns the value in the current thread's copy of this
- * thread-local variable. If the variable has no value for the
- * current thread, it is first initialized to the value returned
- * by an invocation of the {@link #initialValue} method.
+ * 返回当前 ThreadLocal 对象关联的值
*
- * @return the current thread's value of this thread-local
+ * @return
*/
public T get() {
+ // 返回当前 ThreadLocal 所在的线程
Thread t = Thread.currentThread();
+ // 从线程中拿到 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
+ // 从 map 中拿到 entry
ThreadLocalMap.Entry e = map.getEntry(this);
+ // 如果不为空,读取当前 ThreadLocal 中保存的值
if (e != null) {
@SuppressWarnings("unchecked")
- T result = (T)e.value;
+ T result = (T) e.value;
return result;
}
}
+ // 若 map 为空,则对当前线程的 ThreadLocal 进行初始化,最后返回当前的 ThreadLocal 对象关联的初值,即 value
return setInitialValue();
}
/**
- * Variant of set() to establish initialValue. Used instead
- * of set() in case user has overridden the set() method.
+ * 初始化 ThreadLocalMap,并存储键值对 ,最后返回 value
*
- * @return the initial value
+ * @return value
*/
private T setInitialValue() {
+ // 获取为 ThreadLocal 对象设置关联的初值
T value = initialValue();
Thread t = Thread.currentThread();
+ // 返回当前线程 t 持有的 map
ThreadLocalMap map = getMap(t);
- if (map != null)
+ if (map != null) {
map.set(this, value);
- else
+ } else {
+ // 为当前线程初始化 map,并存储键值对
createMap(t, value);
+ }
return value;
}
/**
- * Sets the current thread's copy of this thread-local variable
- * to the specified value. Most subclasses will have no need to
- * override this method, relying solely on the {@link #initialValue}
- * method to set the values of thread-locals.
+ * 为当前 ThreadLocal 对象关联 value 值
*
- * @param value the value to be stored in the current thread's copy of
- * this thread-local.
+ * @param value 要存储在此线程的线程副本的值
*/
public void set(T value) {
+ // 返回当前 ThreadLocal 所在的线程
Thread t = Thread.currentThread();
+ // 返回当前线程持有的map
ThreadLocalMap map = getMap(t);
- if (map != null)
+ if (map != null) {
+ // 如果 ThreadLocalMap 不为空,则直接存储键值对
map.set(this, value);
- else
+ } else {
+ // 否则,需要为当前线程初始化 ThreadLocalMap,并存储键值对
createMap(t, value);
+ }
}
/**
- * Removes the current thread's value for this thread-local
- * variable. If this thread-local variable is subsequently
- * {@linkplain #get read} by the current thread, its value will be
- * reinitialized by invoking its {@link #initialValue} method,
- * unless its value is {@linkplain #set set} by the current thread
- * in the interim. This may result in multiple invocations of the
- * {@code initialValue} method in the current thread.
- *
- * @since 1.5
+ * 清理当前 ThreadLocal 对象关联的键值对
*/
public void remove() {
+ // 返回当前线程持有的 map
ThreadLocalMap m = getMap(Thread.currentThread());
- if (m != null)
+ if (m != null) {
+ // 从 map 中清理当前 ThreadLocal 对象关联的键值对
m.remove(this);
+ }
}
/**
- * Get the map associated with a ThreadLocal. Overridden in
- * InheritableThreadLocal.
+ * 返回当前线程 thread 持有的 ThreadLocalMap
*
- * @param t the current thread
- * @return the map
+ * @param t 当前线程
+ * @return ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
- * Create the map associated with a ThreadLocal. Overridden in
- * InheritableThreadLocal.
+ * 为当前线程初始化map,并存储键值对
*
- * @param t the current thread
- * @param firstValue value for the initial entry of the map
+ * @param t 当前线程
+ * @param firstValue 要设置的 value 值
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
@@ -261,29 +222,25 @@ protected T initialValue() {
}
/**
- * ThreadLocalMap is a customized hash map suitable only for
- * maintaining thread local values. No operations are exported
- * outside of the ThreadLocal class. The class is package private to
- * allow declaration of fields in class Thread. To help deal with
- * very large and long-lived usages, the hash table entries use
- * WeakReferences for keys. However, since reference queues are not
- * used, stale entries are guaranteed to be removed only when
- * the table starts running out of space.
+ * 类似HashMap,进行元素存取时,要清理遇到的垃圾值,且合并原先紧密相邻的元素(除去垃圾值会造成新空槽)
*/
static class ThreadLocalMap {
/**
- * The entries in this hash map extend WeakReference, using
- * its main ref field as the key (which is always a
- * ThreadLocal object). Note that null keys (i.e. entry.get()
- * == null) mean that the key is no longer referenced, so the
- * entry can be expunged from table. Such entries are referred to
- * as "stale entries" in the code that follows.
+ * 键值对实体的存储结构
*/
static class Entry extends WeakReference> {
- /** The value associated with this ThreadLocal. */
+ /**
+ * 当前线程关联的 value,这个 value 并没有用弱引用追踪
+ */
Object value;
+ /**
+ * 构造键值对
+ *
+ * @param k k 作 key,作为 key 的 ThreadLocal 会被包装为一个弱引用
+ * @param v v 作 value
+ */
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
@@ -291,54 +248,55 @@ static class Entry extends WeakReference> {
}
/**
- * The initial capacity -- MUST be a power of two.
+ * 初始容量,必须为 2 的幂
*/
private static final int INITIAL_CAPACITY = 16;
/**
- * The table, resized as necessary.
- * table.length MUST always be a power of two.
+ * 存储 ThreadLocal 的键值对实体数组,长度必须为 2 的幂
*/
private Entry[] table;
/**
- * The number of entries in the table.
+ * ThreadLocalMap 元素数量
*/
private int size = 0;
/**
- * The next size value at which to resize.
+ * 扩容的阈值,默认是数组大小的三分之二
*/
- private int threshold; // Default to 0
+ private int threshold;
/**
- * Set the resize threshold to maintain at worst a 2/3 load factor.
+ * 设置扩容阙值
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
- * Increment i modulo len.
+ * 哈希值发生冲突时,计算下一个哈希值,此处使用线性探测寻址,只是简单地将索引加 1
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
- * Decrement i modulo len.
+ * 线性探测,向前遍历
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
- * Construct a new map initially containing (firstKey, firstValue).
- * ThreadLocalMaps are constructed lazily, so we only create
- * one when we have at least one entry to put in it.
+ * 初始化 ThreadLocalMap,并存储键值对
+ *
+ * @param firstKey
+ * @param firstValue
*/
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
+ // i 是一个 [0, INITIAL_CAPACITY) 之间的值
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
@@ -376,27 +334,25 @@ private ThreadLocalMap(ThreadLocalMap parentMap) {
}
/**
- * Get the entry associated with key. This method
- * itself handles only the fast path: a direct hit of existing
- * key. It otherwise relays to getEntryAfterMiss. This is
- * designed to maximize performance for direct hits, in part
- * by making this method readily inlinable.
+ * 返回 key 关联的键值对实体
*
- * @param key the thread local object
- * @return the entry associated with key, or null if no such
+ * @param key threadLocal
+ * @return
*/
private Entry getEntry(ThreadLocal> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
- if (e != null && e.get() == key)
+ // 若 e 不为空,并且 e 的 ThreadLocal 的内存地址和 key 相同,直接返回
+ if (e != null && e.get() == key) {
return e;
- else
+ } else {
+ // 从 i 开始向后遍历找到键值对实体
return getEntryAfterMiss(key, i, e);
+ }
}
/**
- * Version of getEntry method for use when key is not found in
- * its direct hash slot.
+ * 从 i 开始向后遍历找到键值对实体
*
* @param key the thread local object
* @param i the table index for key's hash code
@@ -409,68 +365,75 @@ private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {
while (e != null) {
ThreadLocal> k = e.get();
- if (k == key)
+ if (k == key) {
return e;
- if (k == null)
+ }
+ // 遇到了垃圾值
+ if (k == null) {
+ // 从索引 i 开始,遍历一段连续的元素,清理其中的垃圾值,并使各元素排序更紧凑
expungeStaleEntry(i);
- else
+ } else {
i = nextIndex(i, len);
+ }
e = tab[i];
}
return null;
}
/**
- * Set the value associated with key.
+ * 在 map 中存储键值对
*
- * @param key the thread local object
- * @param value the value to be set
+ * @param key threadLocal
+ * @param value 要设置的 value 值
*/
private void set(ThreadLocal> key, Object value) {
-
- // We don't use a fast path as with get() because it is at
- // least as common to use set() to create new entries as
- // it is to replace existing ones, in which case, a fast
- // path would fail more often than not.
-
Entry[] tab = table;
int len = tab.length;
- int i = key.threadLocalHashCode & (len-1);
-
- for (Entry e = tab[i];
- e != null;
- e = tab[i = nextIndex(i, len)]) {
+ // 计算 key 在数组中的下标
+ int i = key.threadLocalHashCode & (len - 1);
+ // 遍历一段连续的元素,以查找匹配的 ThreadLocal 对象
+ for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
+ // 获取该哈希值处的ThreadLocal对象
ThreadLocal> k = e.get();
+ // 键值ThreadLocal匹配,直接更改map中的value
if (k == key) {
e.value = value;
return;
}
+ // 若 key 是 null,说明 ThreadLocal 被清理了,直接替换掉
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
+ // 直到遇见了空槽也没找到匹配的ThreadLocal对象,那么在此空槽处安排ThreadLocal对象和缓存的value
tab[i] = new Entry(key, value);
int sz = ++size;
- if (!cleanSomeSlots(i, sz) && sz >= threshold)
+ // 如果没有元素被清理,那么就要检查当前元素数量是否超过了容量阙值(数组大小的三分之二),以便决定是否扩容
+ if (!cleanSomeSlots(i, sz) && sz >= threshold) {
+ // 扩容的过程也是对所有的 key 重新哈希的过程
rehash();
+ }
}
/**
- * Remove the entry for key.
+ * 从 map 中清理 key 关联的键值对
+ *
+ * @param key
*/
private void remove(ThreadLocal> key) {
Entry[] tab = table;
int len = tab.length;
- int i = key.threadLocalHashCode & (len-1);
+ int i = key.threadLocalHashCode & (len - 1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
+ // 从索引 i 开始,遍历一段连续的元素,清理其中的垃圾值,并使各元素排序更紧凑
expungeStaleEntry(i);
return;
}
@@ -551,32 +514,28 @@ private void replaceStaleEntry(ThreadLocal> key, Object value,
}
/**
- * Expunge a stale entry by rehashing any possibly colliding entries
- * lying between staleSlot and the next null slot. This also expunges
- * any other stale entries encountered before the trailing null. See
- * Knuth, Section 6.4
+ * 从索引staleSlot开始,遍历一段连续的元素,清理其中的垃圾值,并使各元素排序更紧凑
*
- * @param staleSlot index of slot known to have null key
- * @return the index of the next null slot after staleSlot
- * (all between staleSlot and this slot will have been checked
- * for expunging).
+ * @param staleSlot
+ * @return 终止遍历过程的空槽下标
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
- // expunge entry at staleSlot
+ // 索引 staleSlot 处本身标识的就是一个垃圾值,所以需要首先清理掉
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
- // Rehash until we encounter null
Entry e;
int i;
+ // 继续往后遍历连续的Entry数组,直到遇见一个空槽后停止遍历
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal> k = e.get();
+ // 如果当前Entry已经不包含 ThreadLocal,说明这是个垃圾值,需要清理
if (k == null) {
e.value = null;
tab[i] = null;
@@ -651,31 +610,37 @@ private void rehash() {
}
/**
- * Double the capacity of the table.
+ * 扩容,重新计算索引,标记垃圾值,方便 GC 回收
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
+ // 新建一个数组,按照2倍长度扩容
Entry[] newTab = new Entry[newLen];
int count = 0;
+ // 将旧数组的值拷贝到新数组上
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal> k = e.get();
+ // 若有垃圾值,则标记清理该元素的引用,以便GC回收
if (k == null) {
- e.value = null; // Help the GC
+ e.value = null;
} else {
+ // 计算 ThreadLocal 在新数组中的位置
int h = k.threadLocalHashCode & (newLen - 1);
- while (newTab[h] != null)
+ // 如果发生冲突,使用线性探测往后寻找合适的位置
+ while (newTab[h] != null) {
h = nextIndex(h, newLen);
+ }
newTab[h] = e;
count++;
}
}
}
-
+ // 设置新的扩容阈值,为数组长度的三分之二
setThreshold(newLen);
size = count;
table = newTab;
diff --git a/src/java/lang/ref/WeakReference.java b/src/java/lang/ref/WeakReference.java
index 707dba58..f9fd6688 100644
--- a/src/java/lang/ref/WeakReference.java
+++ b/src/java/lang/ref/WeakReference.java
@@ -1,69 +1,29 @@
-/*
- * Copyright (c) 1997, 2003, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
package java.lang.ref;
-
/**
- * Weak reference objects, which do not prevent their referents from being
- * made finalizable, finalized, and then reclaimed. Weak references are most
- * often used to implement canonicalizing mappings.
- *
- * Suppose that the garbage collector determines at a certain point in time
- * that an object is weakly
- * reachable. At that time it will atomically clear all weak references to
- * that object and all weak references to any other weakly-reachable objects
- * from which that object is reachable through a chain of strong and soft
- * references. At the same time it will declare all of the formerly
- * weakly-reachable objects to be finalizable. At the same time or at some
- * later time it will enqueue those newly-cleared weak references that are
- * registered with reference queues.
+ * 弱引用(Weak Reference)
+ * 当一个对象为弱引用时,运行 GC 后会回收其引用指向的对象
+ * 它可以用于解决非静态内部类的内存泄露问题(定义一个静态内部类,并让它持有外部类的弱引用)
+ * 还可以用于实现缓存,比如 WeakHashMap
*
- * @author Mark Reinhold
- * @since 1.2
+ * @param
*/
-
public class WeakReference extends Reference {
/**
- * Creates a new weak reference that refers to the given object. The new
- * reference is not registered with any queue.
+ * 对 referent 对象进行弱引用
*
- * @param referent object the new weak reference will refer to
+ * @param referent 被弱引用的对象
*/
public WeakReference(T referent) {
super(referent);
}
/**
- * Creates a new weak reference that refers to the given object and is
- * registered with the given queue.
+ * 对 referent 对象进行弱引用,在对象被回收后,会把弱引用对象,也就是 WeakReference 对象或者其子类的对象,放入队列 ReferenceQueue 中
*
- * @param referent object the new weak reference will refer to
- * @param q the queue with which the reference is to be registered,
- * or null if registration is not required
+ * @param referent 被弱引用的对象
+ * @param q 引用队列
*/
public WeakReference(T referent, ReferenceQueue super T> q) {
super(referent, q);
From ce363494b1c5a08fcaeb5eb85f98ffe311e933cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=B3=E9=98=B3?= <260893248@qq.com>
Date: Fri, 1 May 2020 10:37:56 +0800
Subject: [PATCH 2/6] =?UTF-8?q?=E6=95=B0->=E6=A0=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
数->树
---
src/java/util/concurrent/ConcurrentHashMap.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/java/util/concurrent/ConcurrentHashMap.java b/src/java/util/concurrent/ConcurrentHashMap.java
index dcb90d0b..0b6047ba 100644
--- a/src/java/util/concurrent/ConcurrentHashMap.java
+++ b/src/java/util/concurrent/ConcurrentHashMap.java
@@ -46,7 +46,7 @@ public class ConcurrentHashMap extends AbstractMap
private static final float LOAD_FACTOR = 0.75f;
/**
- * 链表转树的阀值,如果table[i]下面的链表长度大于8时就转化为数
+ * 链表转树的阀值,如果table[i]下面的链表长度大于8时就转化为树
*/
static final int TREEIFY_THRESHOLD = 8;
From 2e88f5137026ada5ac13cd848c3d7bdcb9f89adf Mon Sep 17 00:00:00 2001
From: wupeixuan <18603203910@163.com>
Date: Mon, 15 Jun 2020 01:10:15 +0800
Subject: [PATCH 3/6] =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E6=B1=A0=E5=A4=8D?=
=?UTF-8?q?=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../util/concurrent/ThreadPoolExecutor.java | 111 ++++++------------
1 file changed, 36 insertions(+), 75 deletions(-)
diff --git a/src/java/util/concurrent/ThreadPoolExecutor.java b/src/java/util/concurrent/ThreadPoolExecutor.java
index d1f59291..e2fba877 100644
--- a/src/java/util/concurrent/ThreadPoolExecutor.java
+++ b/src/java/util/concurrent/ThreadPoolExecutor.java
@@ -1,38 +1,3 @@
-/*
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
-/*
- *
- *
- *
- *
- *
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
package java.util.concurrent;
import java.security.AccessControlContext;
@@ -905,11 +870,9 @@ private boolean addWorker(Runnable firstTask, boolean core) {
int rs = runStateOf(c);
// Check if queue empty only if necessary.
- if (rs >= SHUTDOWN &&
- ! (rs == SHUTDOWN &&
- firstTask == null &&
- ! workQueue.isEmpty()))
+ if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) {
return false;
+ }
for (;;) {
int wc = workerCountOf(c);
@@ -953,6 +916,7 @@ private boolean addWorker(Runnable firstTask, boolean core) {
} finally {
mainLock.unlock();
}
+ // 如果成功添加了 Worker,就可以启动 Worker 了
if (workerAdded) {
t.start();
workerStarted = true;
@@ -1069,9 +1033,7 @@ private Runnable getTask() {
}
try {
- Runnable r = timed ?
- workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
- workQueue.take();
+ Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if (r != null)
return r;
timedOut = true;
@@ -1133,15 +1095,9 @@ final void runWorker(Worker w) {
try {
while (task != null || (task = getTask()) != null) {
w.lock();
- // If pool is stopping, ensure thread is interrupted;
- // if not, ensure thread is not interrupted. This
- // requires a recheck in second case to deal with
- // shutdownNow race while clearing interrupt
- if ((runStateAtLeast(ctl.get(), STOP) ||
- (Thread.interrupted() &&
- runStateAtLeast(ctl.get(), STOP))) &&
- !wt.isInterrupted())
+ if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) {
wt.interrupt();
+ }
try {
beforeExecute(wt, task);
Throwable thrown = null;
@@ -1340,43 +1296,48 @@ public ThreadPoolExecutor(int corePoolSize,
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
- if (command == null)
+ // 若任务为空,则抛 NPE,不能执行空任务
+ if (command == null) {
throw new NullPointerException();
- /*
- * Proceed in 3 steps:
- *
- * 1. If fewer than corePoolSize threads are running, try to
- * start a new thread with the given command as its first
- * task. The call to addWorker atomically checks runState and
- * workerCount, and so prevents false alarms that would add
- * threads when it shouldn't, by returning false.
- *
- * 2. If a task can be successfully queued, then we still need
- * to double-check whether we should have added a thread
- * (because existing ones died since last checking) or that
- * the pool shut down since entry into this method. So we
- * recheck state and if necessary roll back the enqueuing if
- * stopped, or start a new thread if there are none.
- *
- * 3. If we cannot queue task, then we try to add a new
- * thread. If it fails, we know we are shut down or saturated
- * and so reject the task.
- */
+ }
int c = ctl.get();
+ // 若工作线程数小于核心线程数,则创建新的线程,并把当前任务 command 作为这个线程的第一个任务
if (workerCountOf(c) < corePoolSize) {
- if (addWorker(command, true))
+ if (addWorker(command, true)) {
return;
+ }
c = ctl.get();
}
+ /**
+ * 至此,有以下两种情况:
+ * 1.当前工作线程数大于等于核心线程数
+ * 2.新建线程失败
+ * 此时会尝试将任务添加到阻塞队列 workQueue
+ */
+ // 若线程池处于 RUNNING 状态,将任务添加到阻塞队列 workQueue 中
if (isRunning(c) && workQueue.offer(command)) {
+ // 再次检查线程池标记
int recheck = ctl.get();
- if (! isRunning(recheck) && remove(command))
+ // 如果线程池已不处于 RUNNING 状态,那么移除已入队的任务,并且执行拒绝策略
+ if (!isRunning(recheck) && remove(command)) {
+ // 任务添加到阻塞队列失败,执行拒绝策略
reject(command);
- else if (workerCountOf(recheck) == 0)
+ }
+ // 如果线程池还是 RUNNING 的,并且线程数为 0,那么开启新的线程
+ else if (workerCountOf(recheck) == 0) {
addWorker(null, false);
+ }
}
- else if (!addWorker(command, false))
+ /**
+ * 至此,有以下两种情况:
+ * 1.线程池处于非运行状态,线程池不再接受新的线程
+ * 2.线程处于运行状态,但是阻塞队列已满,无法加入到阻塞队列
+ * 此时会尝试以最大线程数为限制创建新的工作线程
+ */
+ else if (!addWorker(command, false)) {
+ // 任务进入线程池失败,执行拒绝策略
reject(command);
+ }
}
/**
From c0d94c5f9d1ec97b8a47efab97e5a0dc67310be8 Mon Sep 17 00:00:00 2001
From: wupeixuan <18603203910@163.com>
Date: Mon, 15 Jun 2020 01:18:43 +0800
Subject: [PATCH 4/6] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 688e0801..bc8c5cc3 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,8 @@
Runnable 源码解析
+ThreadPoolExecutor 源码解析
+
## 其他
Object 源码解析
From 8ca22f6093b505d854a85255f9b8f2d2af2d0489 Mon Sep 17 00:00:00 2001
From: wupeixuan <18603203910@163.com>
Date: Mon, 15 Jun 2020 19:15:21 +0800
Subject: [PATCH 5/6] =?UTF-8?q?=E6=B5=8B=E8=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/java/lang/Integer.java | 31 ++-------------
src/java/lang/String.java | 4 +-
src/java/lang/management/MemoryUsage.java | 38 +++++++------------
src/java/util/ArrayList.java | 8 ++--
src/java/util/concurrent/Executors.java | 37 +-----------------
.../util/concurrent/atomic/AtomicInteger.java | 35 -----------------
test/java/lang/IntegerTest.java | 19 ++++++++++
test/java/lang/IntegerTest2.java | 19 ++++++++++
test/java/lang/IntegerTest3.java | 15 ++++++++
test/java/lang/MemoryUsageTest.java | 25 ++++++++++++
test/java/lang/StringTest.java | 21 ++++++++++
11 files changed, 122 insertions(+), 130 deletions(-)
create mode 100644 test/java/lang/IntegerTest.java
create mode 100644 test/java/lang/IntegerTest2.java
create mode 100644 test/java/lang/IntegerTest3.java
create mode 100644 test/java/lang/MemoryUsageTest.java
create mode 100644 test/java/lang/StringTest.java
diff --git a/src/java/lang/Integer.java b/src/java/lang/Integer.java
index 1a3165f9..63f5069c 100644
--- a/src/java/lang/Integer.java
+++ b/src/java/lang/Integer.java
@@ -1,28 +1,3 @@
-/*
- * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
package java.lang;
import java.lang.annotation.Native;
@@ -801,8 +776,9 @@ private static class IntegerCache {
cache = new Integer[(high - low) + 1];
int j = low;
- for(int k = 0; k < cache.length; k++)
+ for(int k = 0; k < cache.length; k++) {
cache[k] = new Integer(j++);
+ }
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
@@ -827,8 +803,9 @@ private IntegerCache() {}
* @since 1.5
*/
public static Integer valueOf(int i) {
- if (i >= IntegerCache.low && i <= IntegerCache.high)
+ if (i >= IntegerCache.low && i <= IntegerCache.high) {
return IntegerCache.cache[i + (-IntegerCache.low)];
+ }
return new Integer(i);
}
diff --git a/src/java/lang/String.java b/src/java/lang/String.java
index fabb7b74..4fdcfd04 100644
--- a/src/java/lang/String.java
+++ b/src/java/lang/String.java
@@ -82,9 +82,7 @@
* @see java.nio.charset.Charset
* @since JDK1.0
*/
-
-public final class String
- implements java.io.Serializable, Comparable, CharSequence {
+public final class String implements java.io.Serializable, Comparable, CharSequence {
/**
* String底层是使用字符数组存储的
*/
diff --git a/src/java/lang/management/MemoryUsage.java b/src/java/lang/management/MemoryUsage.java
index 4bd56756..f002a31a 100644
--- a/src/java/lang/management/MemoryUsage.java
+++ b/src/java/lang/management/MemoryUsage.java
@@ -1,28 +1,3 @@
-/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
package java.lang.management;
import javax.management.openmbean.CompositeData;
@@ -104,9 +79,21 @@
* @since 1.5
*/
public class MemoryUsage {
+ /**
+ * JVM 在启动期间从操作系统请求的用于内存管理的初始内存容量(以字节为单位)
+ */
private final long init;
+ /**
+ * 当前已经使用的内存量(以字节为单位)
+ */
private final long used;
+ /**
+ * 保证可以由 JVM 使用的内存量(以字节为单位)
+ */
private final long committed;
+ /**
+ * 用于内存管理的最大内存量(以字节为单位)
+ */
private final long max;
/**
@@ -236,6 +223,7 @@ public long getMax() {
/**
* Returns a descriptive representation of this memory usage.
*/
+ @Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("init = " + init + "(" + (init >> 10) + "K) ");
diff --git a/src/java/util/ArrayList.java b/src/java/util/ArrayList.java
index ed9fa285..a3c96265 100644
--- a/src/java/util/ArrayList.java
+++ b/src/java/util/ArrayList.java
@@ -71,7 +71,7 @@ public class ArrayList extends AbstractList
/**
* 带有容量initialCapacity的构造方法
*
- * @param 初始容量列表的初始容量
+ * @param initialCapacity 初始容量列表的初始容量
* @throws IllegalArgumentException 如果指定容量为负
*/
public ArrayList(int initialCapacity) {
@@ -84,8 +84,7 @@ public ArrayList(int initialCapacity) {
this.elementData = EMPTY_ELEMENTDATA;
} else {// 小于0
// 则抛出IllegalArgumentException异常
- throw new IllegalArgumentException("Illegal Capacity: " +
- initialCapacity);
+ throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
@@ -107,8 +106,9 @@ public ArrayList(Collection extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toarray可能(错误地)不返回对象[](见JAVA BUG编号6260652)
- if (elementData.getClass() != Object[].class)
+ if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
+ }
} else {
// 使用空数组
this.elementData = EMPTY_ELEMENTDATA;
diff --git a/src/java/util/concurrent/Executors.java b/src/java/util/concurrent/Executors.java
index 422631f7..88bf8491 100644
--- a/src/java/util/concurrent/Executors.java
+++ b/src/java/util/concurrent/Executors.java
@@ -1,38 +1,3 @@
-/*
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
-/*
- *
- *
- *
- *
- *
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
package java.util.concurrent;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -591,7 +556,7 @@ public T run() throws Exception {
}
/**
- * The default thread factory
+ * 默认的线程工厂
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
diff --git a/src/java/util/concurrent/atomic/AtomicInteger.java b/src/java/util/concurrent/atomic/AtomicInteger.java
index 514f0f72..e1c68153 100644
--- a/src/java/util/concurrent/atomic/AtomicInteger.java
+++ b/src/java/util/concurrent/atomic/AtomicInteger.java
@@ -1,38 +1,3 @@
-/*
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
-/*
- *
- *
- *
- *
- *
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
package java.util.concurrent.atomic;
import java.util.function.IntUnaryOperator;
import java.util.function.IntBinaryOperator;
diff --git a/test/java/lang/IntegerTest.java b/test/java/lang/IntegerTest.java
new file mode 100644
index 00000000..eb426c91
--- /dev/null
+++ b/test/java/lang/IntegerTest.java
@@ -0,0 +1,19 @@
+package java.lang;
+
+/**
+ * IntegerCache 会缓存[-128,127]的 Integer(为了自动装箱的复用),valueOf 方法会从缓存中去拿值,如果命中缓存,会减少资源的开销
+ * 对象池模式:空间换时间
+ *
+ * @author wupx
+ * @date 2020/04/26
+ */
+public class IntegerTest {
+ public static void main(String[] args) {
+ Integer i1 = 66;// 通过反编译可以知道 Integer i = 66; 这种形式声明的变量是通过 java.lang.Integer#valueOf(int) 来构造 Integer 对象的
+ Integer i2 = 66;
+ Integer i3 = 128;
+ Integer i4 = 128;
+ System.out.println(i1 == i2);// true,在[-128,127]内时,会直接从 IntegerCache 中获取 Integer
+ System.out.println(i3 == i4);// false,属于两个对象
+ }
+}
\ No newline at end of file
diff --git a/test/java/lang/IntegerTest2.java b/test/java/lang/IntegerTest2.java
new file mode 100644
index 00000000..89cdcb6f
--- /dev/null
+++ b/test/java/lang/IntegerTest2.java
@@ -0,0 +1,19 @@
+package java.lang;
+
+/**
+ * 当一个 Integer 和 int 比较时,Integer 会调用 intValue() 方法自动拆箱后再比较
+ *
+ * @author wupx
+ * @date 2020/04/27
+ */
+public class IntegerTest2 {
+ public static void main(String[] args) {
+ Integer i1 = new Integer(5);
+ Integer i2 = new Integer(5);// 通过 new 来创建的两个 Integer 对象
+ Integer i3 = 5;// 调用 valueOf 将 5 自动装箱成 Integer 类型
+ int i4 = 5;// 基本数据类型 5
+ System.out.println(i1 == i3); // false,两个引用没有引用同一对象
+ System.out.println(i1 == i2); // false,两个通过 new 创建的 Integer 对象不是同一个引用
+ System.out.println(i3 == i4); // true,i3 调用 java/lang/Integer.intValue:()I 自动拆箱成 int 类型再和 i4 比较
+ }
+}
\ No newline at end of file
diff --git a/test/java/lang/IntegerTest3.java b/test/java/lang/IntegerTest3.java
new file mode 100644
index 00000000..f3eaac0a
--- /dev/null
+++ b/test/java/lang/IntegerTest3.java
@@ -0,0 +1,15 @@
+package java.lang;
+
+/**
+ * getInteger 方法从系统属性中获取值,然后再装箱,如果系统属性中没有该值,则返回备用值 val
+ *
+ * @author wupx
+ * @date 2020/04/27
+ */
+public class IntegerTest3 {
+ public static void main(String[] args) {
+ System.setProperty("age", "18");
+ Integer age = Integer.getInteger("age", 25);
+ System.out.println(age);
+ }
+}
\ No newline at end of file
diff --git a/test/java/lang/MemoryUsageTest.java b/test/java/lang/MemoryUsageTest.java
new file mode 100644
index 00000000..94e90100
--- /dev/null
+++ b/test/java/lang/MemoryUsageTest.java
@@ -0,0 +1,25 @@
+package java.lang;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryManagerMXBean;
+import java.lang.management.MemoryUsage;
+import java.util.List;
+
+/**
+ * @author wupx
+ * @date 2020/04/28
+ */
+public class MemoryUsageTest {
+
+ public static void main(String[] args) {
+ MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();
+ MemoryUsage memoryUsage = memoryMxBean.getHeapMemoryUsage();
+ System.out.println(memoryUsage);
+ List memoryManagerMXBeans = ManagementFactory.getMemoryManagerMXBeans();
+ for (MemoryManagerMXBean memoryManagerMXBean : memoryManagerMXBeans) {
+ System.out.println(memoryManagerMXBean.getName());
+ }
+ }
+
+}
diff --git a/test/java/lang/StringTest.java b/test/java/lang/StringTest.java
new file mode 100644
index 00000000..21e527b1
--- /dev/null
+++ b/test/java/lang/StringTest.java
@@ -0,0 +1,21 @@
+package java.lang;
+
+/**
+ * @author wupx
+ * @date 2020/04/27
+ */
+public class StringTest {
+ public static void main(String[] args) {
+// String name = "wupx";
+// String substring = name.substring(0, 1);
+// System.out.println(name);
+// System.out.println(substring);
+ variableParameter("1","2");
+ }
+
+ public static void variableParameter(String... strings){
+ for (String string : strings) {
+ System.out.println(string);
+ }
+ }
+}
From 4113331d8c1d6d2d0bc7f1e04e452e541eb840b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=AD=A6=E5=9F=B9=E8=BD=A9?=
<34578335+wupeixuan@users.noreply.github.com>
Date: Wed, 8 Sep 2021 10:25:43 +0800
Subject: [PATCH 6/6] Update README.md
---
README.md | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/README.md b/README.md
index bc8c5cc3..ea31a328 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,6 @@
专注分享后端技术干货,包括 Java 基础、Java 并发、JVM、Nginx、Zookeeper、微服务、消息队列、源码解析、数据库、设计模式、面经等,助你编程之路少走弯路。
-有一句话说得很好,一个人学习可以走得很快,但一群人学习可以走得更远。
-
-所以,如果你想和众多优秀的人一起学习,可以考虑加入技术交流群。扫描下方微信二维码,备注【加群】添加好友,我会迅速拉你进群。
-
-
-
## 基础
String 源码
@@ -46,4 +40,4 @@
InheritableThreadLocal 源码解析
-WeakReference 源码解析
\ No newline at end of file
+WeakReference 源码解析