Showing preview only (724K chars total). Download the full file or copy to clipboard to get everything.
Repository: crossoverJie/JCSprout
Branch: master
Commit: fc4c6e5f6d17
Files: 221
Total size: 15.7 MB
Directory structure:
gitextract_c7550992/
├── .github/
│ ├── ISSUE_TEMPLATE
│ └── PULL_REQUEST_TEMPLATE
├── .travis.yml
├── 79884.log
├── LICENSE
├── MD/
│ ├── ArrayList.md
│ ├── Cache-design.md
│ ├── ClassLoad.md
│ ├── ConcurrentHashMap.md
│ ├── Consistent-Hash.md
│ ├── DB-split.md
│ ├── GarbageCollection.md
│ ├── HashMap.md
│ ├── ID-generator.md
│ ├── Java-lock.md
│ ├── Limiting.md
│ ├── LinkedList.md
│ ├── MemoryAllocation.md
│ ├── MySQL-Index.md
│ ├── OOM-analysis.md
│ ├── ReentrantLock.md
│ ├── SQL-optimization.md
│ ├── Spike.md
│ ├── SpringAOP.md
│ ├── Synchronize.md
│ ├── TCP-IP.md
│ ├── Thread-common-problem.md
│ ├── ThreadPoolExecutor.md
│ ├── Threadcore.md
│ ├── additional-skills/
│ │ └── how-to-use-git-efficiently.md
│ ├── architecture-design/
│ │ └── million-sms-push.md
│ ├── collection/
│ │ ├── HashSet.md
│ │ └── LinkedHashMap.md
│ ├── concurrent/
│ │ ├── thread-communication.md
│ │ └── volatile.md
│ ├── distributed/
│ │ ├── Distributed-Limit.md
│ │ └── distributed-lock-redis.md
│ ├── jvm/
│ │ └── OOM-Disruptor.md
│ ├── kafka/
│ │ └── kafka-product.md
│ ├── newObject.md
│ ├── soft-skills/
│ │ ├── Interview-experience.md
│ │ └── how-to-be-developer.md
│ ├── spring/
│ │ └── spring-bean-lifecycle.md
│ └── third-party-component/
│ ├── cicada.md
│ ├── guava-cache.md
│ └── seconds-kill.md
├── README.md
├── docs/
│ ├── .nojekyll
│ ├── README.md
│ ├── _coverpage.md
│ ├── _sidebar.md
│ ├── algorithm/
│ │ ├── Consistent-Hash.md
│ │ ├── LRU-cache.md
│ │ ├── Limiting.md
│ │ ├── common-algorithm.md
│ │ ├── consistent-hash-implement.md
│ │ └── guava-bloom-filter.md
│ ├── architecture-design/
│ │ ├── Spike.md
│ │ ├── million-sms-push.md
│ │ └── seconds-kill.md
│ ├── collections/
│ │ ├── ArrayList.md
│ │ ├── HashMap.md
│ │ ├── HashSet.md
│ │ ├── LinkedHashMap.md
│ │ └── LinkedList.md
│ ├── contactme.md
│ ├── db/
│ │ ├── DB-split.md
│ │ ├── MySQL-Index.md
│ │ ├── SQL-optimization.md
│ │ └── sharding-db.md
│ ├── distributed/
│ │ ├── Cache-design.md
│ │ ├── Distributed-Limit.md
│ │ ├── ID-generator.md
│ │ └── distributed-lock-redis.md
│ ├── frame/
│ │ ├── SpringAOP.md
│ │ ├── guava-cache.md
│ │ ├── kafka-consumer.md
│ │ ├── kafka-product.md
│ │ └── spring-bean-lifecycle.md
│ ├── index.html
│ ├── jvm/
│ │ ├── ClassLoad.md
│ │ ├── GarbageCollection.md
│ │ ├── JVM-concurrent-HashSet-problem.md
│ │ ├── MemoryAllocation.md
│ │ ├── OOM-Disruptor.md
│ │ ├── OOM-analysis.md
│ │ ├── cpu-percent-100.md
│ │ ├── newObject.md
│ │ └── volatile.md
│ ├── netty/
│ │ ├── Netty(1)TCP-Heartbeat.md
│ │ ├── Netty(2)Thread-model.md
│ │ ├── cicada.md
│ │ └── cim.md
│ ├── soft-skills/
│ │ ├── Interview-experience.md
│ │ ├── TCP-IP.md
│ │ ├── how-to-be-developer.md
│ │ └── how-to-use-git-efficiently.md
│ └── thread/
│ ├── ArrayBlockingQueue.md
│ ├── ConcurrentHashMap.md
│ ├── Java-lock.md
│ ├── ReentrantLock.md
│ ├── Synchronize.md
│ ├── Thread-common-problem.md
│ ├── ThreadPoolExecutor.md
│ ├── Threadcore.md
│ ├── thread-communication.md
│ ├── thread-gone.md
│ └── thread-gone2.md
├── java_pid26365.hprof
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── crossoverjie/
│ │ ├── Application.java
│ │ ├── actual/
│ │ │ ├── FourThreadPrinter.java
│ │ │ ├── LRUAbstractMap.java
│ │ │ ├── LRULinkedMap.java
│ │ │ ├── LRUMap.java
│ │ │ ├── NotifyAll.java
│ │ │ ├── ReadFile.java
│ │ │ ├── Search.java
│ │ │ ├── ThreadCommunication.java
│ │ │ ├── TwoThread.java
│ │ │ ├── TwoThreadNonBlocking.java
│ │ │ ├── TwoThreadWaitNotify.java
│ │ │ └── TwoThreadWaitNotifySimple.java
│ │ ├── algorithm/
│ │ │ ├── ArrayKShift.java
│ │ │ ├── BinaryNode.java
│ │ │ ├── BinaryNodeTravel.java
│ │ │ ├── BloomFilters.java
│ │ │ ├── HappyNum.java
│ │ │ ├── LinkLoop.java
│ │ │ ├── LinkedListMergeSort.java
│ │ │ ├── MergeTwoSortedLists.java
│ │ │ ├── ReverseNode.java
│ │ │ ├── TwoArray.java
│ │ │ ├── TwoStackQueue.java
│ │ │ └── TwoSum.java
│ │ ├── basic/
│ │ │ ├── CollectionsTest.java
│ │ │ ├── HashMapTest.java
│ │ │ └── StringTest.java
│ │ ├── classloader/
│ │ │ ├── ChildClass.java
│ │ │ ├── Main.java
│ │ │ └── SuperClass.java
│ │ ├── concurrent/
│ │ │ ├── ArrayQueue.java
│ │ │ ├── CustomThreadPool.java
│ │ │ ├── Singleton.java
│ │ │ ├── StopThread.java
│ │ │ ├── ThreadState.java
│ │ │ ├── Volatile.java
│ │ │ ├── VolatileInc.java
│ │ │ ├── communication/
│ │ │ │ ├── MultipleThreadCountDownKit.java
│ │ │ │ └── Notify.java
│ │ │ └── future/
│ │ │ ├── Callable.java
│ │ │ ├── Future.java
│ │ │ └── FutureTask.java
│ │ ├── design/
│ │ │ └── pattern/
│ │ │ ├── chainofresponsibility/
│ │ │ │ ├── Main.java
│ │ │ │ ├── MsgProcessChain.java
│ │ │ │ ├── Process.java
│ │ │ │ └── impl/
│ │ │ │ ├── CopyrightProcess.java
│ │ │ │ ├── SensitiveWordProcess.java
│ │ │ │ └── TypoProcess.java
│ │ │ └── factorymethod/
│ │ │ ├── Animal.java
│ │ │ ├── AnimalFactory.java
│ │ │ ├── Cat.java
│ │ │ ├── CatFactory.java
│ │ │ ├── Fish.java
│ │ │ ├── FishFactory.java
│ │ │ └── Main.java
│ │ ├── disruptor/
│ │ │ ├── LongEvent.java
│ │ │ ├── LongEventFactory.java
│ │ │ ├── LongEventHandler.java
│ │ │ ├── LongEventMain.java
│ │ │ └── LongEventProducer.java
│ │ ├── gc/
│ │ │ └── MinorGC.java
│ │ ├── guava/
│ │ │ ├── CacheLoaderTest.java
│ │ │ └── callback/
│ │ │ ├── CallBackListener.java
│ │ │ ├── Caller.java
│ │ │ ├── Main.java
│ │ │ └── Notifier.java
│ │ ├── hystrix/
│ │ │ ├── CommandOrder.java
│ │ │ ├── CommandTest.java
│ │ │ └── CommandUser.java
│ │ ├── oom/
│ │ │ └── heap/
│ │ │ ├── HeapOOM.java
│ │ │ └── MetaSpaceOOM.java
│ │ ├── proxy/
│ │ │ ├── cglib/
│ │ │ │ ├── RealSubject.java
│ │ │ │ └── RealSubjectIntercept.java
│ │ │ └── jdk/
│ │ │ ├── CustomizeHandle.java
│ │ │ ├── ISubject.java
│ │ │ └── impl/
│ │ │ └── ISubjectImpl.java
│ │ ├── red/
│ │ │ └── RedPacket.java
│ │ ├── spring/
│ │ │ ├── LifeCycleConfig.java
│ │ │ ├── SpringLifeCycle.java
│ │ │ ├── annotation/
│ │ │ │ └── AnnotationBean.java
│ │ │ ├── aware/
│ │ │ │ └── SpringLifeCycleAware.java
│ │ │ ├── processor/
│ │ │ │ └── SpringLifeCycleProcessor.java
│ │ │ └── service/
│ │ │ └── SpringLifeCycleService.java
│ │ ├── synchronize/
│ │ │ └── Synchronize.java
│ │ └── thread/
│ │ └── ThreadExceptionTest.java
│ └── resources/
│ ├── application.properties
│ └── logback.xml
└── test/
└── java/
└── com/
└── crossoverjie/
├── actual/
│ ├── AbstractMapTest.java
│ ├── LRULinkedMapTest.java
│ └── LRUMapTest.java
├── algorithm/
│ ├── BinaryNodeTest.java
│ ├── BinaryNodeTravelTest.java
│ ├── BloomFiltersTest.java
│ ├── HappyNumTest.java
│ ├── LinkLoopTest.java
│ ├── LinkedListMergeSortTest.java
│ ├── MergeTwoSortedListsTest.java
│ ├── ReverseNodeTest.java
│ ├── TwoStackQueueTest.java
│ └── TwoSumTest.java
├── concurrent/
│ ├── ArrayQueueTest.java
│ ├── CustomThreadPoolExeceptionTest.java
│ ├── CustomThreadPoolFutureTest.java
│ ├── CustomThreadPoolTest.java
│ ├── MultipleThreadCountDownKitTest.java
│ └── ThreadPoolTest.java
├── kafka/
│ └── KafkaTest.java
├── proxy/
│ └── JDKProxyTest.java
├── red/
│ └── RedPacketTest.java
└── reference/
└── ReferenceTest.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE
================================================
**在提交issue之前请回答以下问题,谢谢!**
> 建议首先查看是否已经有类似的 Issues (提交时可删除该提示)
### 你使用的是哪个版本
### 预期结果
### 实际结果
### 重现结果的步骤
### 其他相关信息
================================================
FILE: .github/PULL_REQUEST_TEMPLATE
================================================
如果是文字类 PR,请按照 [中文排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) 进行编写(提交时可删除该提示)。
**What kind of change does this PR introduce?** (check at least one)
- [ ] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:
**The description of the PR:**
**Other information:**
================================================
FILE: .travis.yml
================================================
language: java
install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true
script: mvn -DskipTests=true clean install
branches:
only:
- master
================================================
FILE: 79884.log
================================================
2018-03-06 23:11:05
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):
"Attach Listener" #15 daemon prio=9 os_prio=31 tid=0x00007ffd7b08b800 nid=0x1407 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #14 prio=5 os_prio=31 tid=0x00007ffd7b001800 nid=0x1c03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Blocked2" #13 prio=5 os_prio=31 tid=0x00007ffd7b08b000 nid=0x5503 waiting for monitor entry [0x00007000083d1000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.crossoverjie.thread.ThreadState$Blocked.run(ThreadState.java:59)
- waiting to lock <0x000000079576cd60> (a java.lang.Class for com.crossoverjie.thread.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
"Blocked1" #12 prio=5 os_prio=31 tid=0x00007ffd7b08a000 nid=0x5303 waiting on condition [0x00007000082ce000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.crossoverjie.thread.ThreadState$Blocked.run(ThreadState.java:59)
- locked <0x000000079576cd60> (a java.lang.Class for com.crossoverjie.thread.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
"Waiting" #11 prio=5 os_prio=31 tid=0x00007ffd7b089800 nid=0x5103 in Object.wait() [0x00007000081cb000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795768db0> (a java.lang.Class for com.crossoverjie.thread.ThreadState$Waiting)
at java.lang.Object.wait(Object.java:502)
at com.crossoverjie.thread.ThreadState$Waiting.run(ThreadState.java:42)
- locked <0x0000000795768db0> (a java.lang.Class for com.crossoverjie.thread.ThreadState$Waiting)
at java.lang.Thread.run(Thread.java:748)
"TimeWaiting" #10 prio=5 os_prio=31 tid=0x00007ffd7b82c800 nid=0x4f03 waiting on condition [0x00007000080c8000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.crossoverjie.thread.ThreadState$TimeWaiting.run(ThreadState.java:27)
at java.lang.Thread.run(Thread.java:748)
"Monitor Ctrl-Break" #9 daemon prio=5 os_prio=31 tid=0x00007ffd7a97e000 nid=0x4d03 runnable [0x0000700007fc5000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at com.intellij.rt.execution.application.AppMain$1.run(AppMain.java:79)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #8 daemon prio=9 os_prio=31 tid=0x00007ffd7a837800 nid=0x4903 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread2" #7 daemon prio=9 os_prio=31 tid=0x00007ffd7b030800 nid=0x4703 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #6 daemon prio=9 os_prio=31 tid=0x00007ffd7b029800 nid=0x4503 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #5 daemon prio=9 os_prio=31 tid=0x00007ffd7a813800 nid=0x4303 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007ffd7a839000 nid=0x4103 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007ffd7b00b800 nid=0x3103 in Object.wait() [0x00007000078b0000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795588ec8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x0000000795588ec8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007ffd7a800800 nid=0x2f03 in Object.wait() [0x00007000077ad000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795586b68> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000795586b68> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=31 tid=0x00007ffd7a01a800 nid=0x2d03 runnable
"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007ffd7b005800 nid=0x2503 runnable
"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007ffd7b006000 nid=0x2703 runnable
"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007ffd7b006800 nid=0x2903 runnable
"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007ffd7b808000 nid=0x2b03 runnable
"VM Periodic Task Thread" os_prio=31 tid=0x00007ffd7a03e000 nid=0x4b03 waiting on condition
JNI global references: 21
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 crossoverJie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MD/ArrayList.md
================================================
# ArrayList/Vector 的底层分析
## ArrayList
`ArrayList` 实现于 `List`、`RandomAccess` 接口。可以插入空数据,也支持随机访问。
`ArrayList `相当于动态数据,其中最重要的两个属性分别是:
`elementData` 数组,以及 `size` 大小。
在调用 `add()` 方法的时候:
```java
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
```
- 首先进行扩容校验。
- 将插入的值放到尾部,并将 size + 1 。
如果是调用 `add(index,e)` 在指定位置添加的话:
```java
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//复制,向后移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
```
- 也是首先扩容校验。
- 接着对数据进行复制,目的是把 index 位置空出来放本次插入的数据,并将后面的数据向后移动一个位置。
其实扩容最终调用的代码:
```java
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
```
也是一个数组复制的过程。
由此可见 `ArrayList` 的主要消耗是数组扩容以及在指定位置添加数据,在日常使用时最好是指定大小,尽量减少扩容。更要减少在指定位置插入数据的操作。
### 序列化
由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用。因此使用了 `transient` 修饰,可以防止被自动序列化。
```java
transient Object[] elementData;
```
因此 ArrayList 自定义了序列化与反序列化:
```java
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
//只序列化了被使用的数据
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
```
> 当对象中自定义了 writeObject 和 readObject 方法时,JVM 会调用这两个自定义方法来实现序列化与反序列化。
从实现中可以看出 ArrayList 只序列化了被使用的数据。
## Vector
`Vector` 也是实现于 `List` 接口,底层数据结构和 `ArrayList` 类似,也是一个动态数组存放数据。不过是在 `add()` 方法的时候使用 `synchronized` 进行同步写数据,但是开销较大,所以 `Vector` 是一个同步容器并不是一个并发容器。
以下是 `add()` 方法:
```java
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
```
以及指定位置插入数据:
```java
public void add(int index, E element) {
insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
```
================================================
FILE: MD/Cache-design.md
================================================
# 分布式缓存设计
目前常见的缓存方案都是分层缓存,通常可以分为以下几层:
- `NG` 本地缓存,命中的话直接返回。
- `NG` 没有命中时则需要查询分布式缓存,如 `Redis` 。
- 如果分布式缓存没有命中则需要回源到 `Tomcat` 在本地堆进行查询,命中之后异步写回 `Redis` 。
- 以上都没有命中那就只有从 `DB` 或者是数据源进行查询,并写回到 Redis 中。
## 缓存更新的原子性
在写回 Redis 的时候如果是 `Tomcat` 集群,多个进程同时写那很有可能出现脏数据,这时就会出现更新原子性的问题。
可以有以下解决方案:
- 可以将多个 Tomcat 中的数据写入到 MQ 队列中,由消费者进行单线程更新缓存。
- 利用[分布式锁](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Java-lock.md#%E5%9F%BA%E4%BA%8E%E6%95%B0%E6%8D%AE%E5%BA%93),只有获取到锁进程才能写数据。
## 如何写缓存
写缓存时也要注意,通常来说分为以下几步:
- 开启事务。
- 写入 DB 。
- 提交事务。
- 写入缓存。
这里可能会存在数据库写入成功但是缓存写入失败的情况,但是也不建议将写入缓存加入到事务中。
因为写缓存的时候可能会因为网络原因耗时较长,这样会阻塞数据库事务。
如果对一致性要求不高并且数据量也不大的情况下,可以单独起一个服务来做 DB 和缓存之间的数据同步操作。
更新缓存时也建议做增量更新。
## 负载策略
缓存负载策略一般有以下两种:
- 轮询机制。
- 一致哈希算法。
轮询的优点是负载到各个服务器的请求是均匀的,但是如果进行扩容则缓存命中率会下降。
一致哈希的优点是相同的请求会负载到同一台服务器上,命中率不会随着扩容而降低,但是当大流量过来时有可能把服务器拖垮。
所以建议两种方案都采用:
首先采用一致哈希算法,当流量达到一定的阈值的时候则切换为轮询,这样既能保证缓存命中率,也能提高系统的可用性。
================================================
FILE: MD/ClassLoad.md
================================================
# 类加载机制
## 双亲委派模型
模型如下图:

双亲委派模型中除了启动类加载器之外其余都需要有自己的父类加载器
当一个类收到了类加载请求时: 自己不会首先加载,而是委派给父加载器进行加载,每个层次的加载器都是这样。
所以最终每个加载请求都会经过启动类加载器。只有当父类加载返回不能加载时子加载器才会进行加载。
双亲委派的好处 : 由于每个类加载都会经过最顶层的启动类加载器,比如 `java.lang.Object`这样的类在各个类加载器下都是同一个类(只有当两个类是由同一个类加载器加载的才有意义,这两个类才相等。)
如果没有双亲委派模型,由各个类加载器自行加载的话。当用户自己编写了一个 `java.lang.Object`类,那样系统中就会出现多个 `Object`,这样 Java 程序中最基本的行为都无法保证,程序会变的非常混乱。
================================================
FILE: MD/ConcurrentHashMap.md
================================================
**更多 HashMap 与 ConcurrentHashMap 相关请查看[这里](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)。**
# ConcurrentHashMap 实现原理
由于 `HashMap` 是一个线程不安全的容器,主要体现在容量大于`总量*负载因子`发生扩容时会出现环形链表从而导致死循环。
因此需要支持线程安全的并发容器 `ConcurrentHashMap` 。
## JDK1.7 实现
### 数据结构

如图所示,是由 `Segment` 数组、`HashEntry` 数组组成,和 `HashMap` 一样,仍然是数组加链表组成。
`ConcurrentHashMap` 采用了分段锁技术,其中 `Segment` 继承于 `ReentrantLock`。不会像 `HashTable` 那样不管是 `put` 还是 `get` 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 `CurrencyLevel` (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 `Segment` 时,不会影响到其他的 `Segment`。
### get 方法
`ConcurrentHashMap` 的 `get` 方法是非常高效的,因为整个过程都不需要加锁。
只需要将 `Key` 通过 `Hash` 之后定位到具体的 `Segment` ,再通过一次 `Hash` 定位到具体的元素上。由于 `HashEntry` 中的 `value` 属性是用 `volatile` 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值([volatile 相关知识点](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Threadcore.md#%E5%8F%AF%E8%A7%81%E6%80%A7))。
### put 方法
内部 `HashEntry` 类 :
```java
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
```
虽然 HashEntry 中的 value 是用 `volatile` 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。
首先也是通过 Key 的 Hash 定位到具体的 Segment,在 put 之前会进行一次扩容校验。这里比 HashMap 要好的一点是:HashMap 是插入元素之后再看是否需要扩容,有可能扩容之后后续就没有插入就浪费了本次扩容(扩容非常消耗性能)。
而 ConcurrentHashMap 不一样,它是在将数据插入之前检查是否需要扩容,之后再做插入操作。
### size 方法
每个 `Segment` 都有一个 `volatile` 修饰的全局变量 `count` ,求整个 `ConcurrentHashMap` 的 `size` 时很明显就是将所有的 `count` 累加即可。但是 `volatile` 修饰的变量却不能保证多线程的原子性,所有直接累加很容易出现并发问题。
但如果每次调用 `size` 方法将其余的修改操作加锁效率也很低。所以做法是先尝试两次将 `count` 累加,如果容器的 `count` 发生了变化再加锁来统计 `size`。
至于 `ConcurrentHashMap` 是如何知道在统计时大小发生了变化呢,每个 `Segment` 都有一个 `modCount` 变量,每当进行一次 `put remove` 等操作,`modCount` 将会 +1。只要 `modCount` 发生了变化就认为容器的大小也在发生变化。
## JDK1.8 实现

1.8 中的 ConcurrentHashMap 数据结构和实现与 1.7 还是有着明显的差异。
其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性。

也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。
其中的 `val next` 都用了 volatile 修饰,保证了可见性。
### put 方法
重点来看看 put 函数:

- 根据 key 计算出 hashcode 。
- 判断是否需要进行初始化。
- `f` 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
- 如果当前位置的 `hashcode == MOVED == -1`,则需要进行扩容。
- 如果都不满足,则利用 synchronized 锁写入数据。
- 如果数量大于 `TREEIFY_THRESHOLD` 则要转换为红黑树。
### get 方法

- 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
- 如果是红黑树那就按照树的方式获取值。
- 都不满足那就按照链表的方式遍历获取值。
## 总结
1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(`O(logn)`),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。
================================================
FILE: MD/Consistent-Hash.md
================================================
# 一致 Hash 算法
当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题:
如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。
## Hash 取模
随机放置就不说了,会带来很多问题。通常最容易想到的方案就是 `hash 取模`了。
可以将传入的 Key 按照 `index = hash(key) % N` 这样来计算出需要存放的节点。其中 hash 函数是一个将字符串转换为正整数的哈希映射方法,N 就是节点的数量。
这样可以满足数据的均匀分配,但是这个算法的容错性和扩展性都较差。
比如增加或删除了一个节点时,所有的 Key 都需要重新计算,显然这样成本较高,为此需要一个算法满足分布均匀同时也要有良好的容错性和拓展性。
## 一致 Hash 算法
一致 Hash 算法是将所有的哈希值构成了一个环,其范围在 `0 ~ 2^32-1`。如下图:

之后将各个节点散列到这个环上,可以用节点的 IP、hostname 这样的唯一性字段作为 Key 进行 `hash(key)`,散列之后如下:

之后需要将数据定位到对应的节点上,使用同样的 `hash 函数` 将 Key 也映射到这个环上。

这样按照顺时针方向就可以把 k1 定位到 `N1节点`,k2 定位到 `N3节点`,k3 定位到 `N2节点`。
### 容错性
这时假设 N1 宕机了:

依然根据顺时针方向,k2 和 k3 保持不变,只有 k1 被重新映射到了 N3。这样就很好的保证了容错性,当一个节点宕机时只会影响到少少部分的数据。
### 拓展性
当新增一个节点时:

在 N2 和 N3 之间新增了一个节点 N4 ,这时会发现受印象的数据只有 k3,其余数据也是保持不变,所以这样也很好的保证了拓展性。
## 虚拟节点
到目前为止该算法依然也有点问题:
当节点较少时会出现数据分布不均匀的情况:

这样会导致大部分数据都在 N1 节点,只有少量的数据在 N2 节点。
为了解决这个问题,一致哈希算法引入了虚拟节点。将每一个节点都进行多次 hash,生成多个节点放置在环上称为虚拟节点:

计算时可以在 IP 后加上编号来生成哈希值。
这样只需要在原有的基础上多一步由虚拟节点映射到实际节点的步骤即可让少量节点也能满足均匀性。
================================================
FILE: MD/DB-split.md
================================================
# 数据库水平垂直拆分
当数据库量非常大的时候,DB 已经成为系统瓶颈时就可以考虑进行水平垂直拆分了。
## 水平拆分
一般水平拆分是根据表中的某一字段(通常是主键 ID )取模处理,将一张表的数据拆分到多个表中。这样每张表的表结构是相同的但是数据不同。
不但可以通过 ID 取模分表还可以通过时间分表,比如每月生成一张表。
按照范围分表也是可行的:一张表只存储 `0~1000W`的数据,超过只就进行分表,这样分表的优点是扩展灵活,但是存在热点数据。
按照取模分表拆分之后我们的查询、修改、删除也都是取模。比如新增一条数据的时候往往需要一张临时表来生成 ID,然后根据生成的 ID 取模计算出需要写入的是哪张表(也可以使用[分布式 ID 生成器](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ID-generator.md)来生成 ID)。
分表之后不能避免的就是查询要比以前复杂,通常不建议 `join` ,一般的做法是做两次查询。
## 垂直拆分
当一张表的字段过多时则可以考虑垂直拆分。
通常是将一张表的字段才分为主表以及扩展表,使用频次较高的字段在一张表,其余的在一张表。
这里的多表查询也不建议使用 `join` ,依然建议使用两次查询。
## 拆分之后带来的问题
拆分之后由一张表变为了多张表,一个库变为了多个库。最突出的一个问题就是事务如何保证。
### 两段提交
### 最终一致性
如果业务对强一致性要求不是那么高那么最终一致性则是一种比较好的方案。
通常的做法就是补偿,比如 一个业务是 A 调用 B,两个执行成功才算最终成功,当 A 成功之后,B 执行失败如何来通知 A 呢。
比较常见的做法是 失败时 B 通过 MQ 将消息告诉 A,A 再来进行回滚。这种的前提是 A 的回滚操作得是幂等的,不然 B 重复发消息就会出现问题。
================================================
FILE: MD/GarbageCollection.md
================================================
# 垃圾回收
> 垃圾回收主要思考三件事情:
- 哪种内存需要回收?
- 什么时候回收?
- 怎么回收?
## 对象是否存活
### 引用计数法
这是一种非常简单易理解的回收算法。每当有一个地方引用一个对象的时候则在引用计数器上 +1,当失效的时候就 -1,无论什么时候计数器为 0 的时候则认为该对象死亡可以回收了。
这种算法虽然简单高效,但是却无法解决**循环引用**的问题,因此 Java 虚拟机并没有采用这种算法。
### 可达性分析算法
主流的语言其实都是采用可达性分析算法:
可达性算法是通过一个称为 `GC Roots` 的对象向下搜索,整个搜索路径就称为引用链,当一个对象到 `GC Roots` 没有任何引用链 `JVM` 就认为该对象是可以被回收的。

如图:Object1、2、3、4 都是存活的对象,而 Object5、6、7都是可回收对象。
可以用作 `GC-Roots` 的对象有:
- 方法区中静态变量所引用的对象。
- 虚拟机栈中所引用的对象。
## 垃圾回收算法
### 标记-清除算法
标记清除算法分为两个步骤,标记和清除。
首先将**不需要回收的对象**标记起来,然后再清除其余可回收对象。但是存在两个主要的问题:
- 标记和清除的效率都不高。
- 清除之后容易出现不连续内存,当需要分配一个较大内存时就不得不需要进行一次垃圾回收。
标记清除过程如下:

### 复制算法
复制算法是将内存划分为两块大小相等的区域,每次使用时都只用其中一块区域,当发生垃圾回收时会将存活的对象全部复制到未使用的区域,然后对之前的区域进行全部回收。
这样简单高效,而且还不存在标记清除算法中的内存碎片问题,但就是有点浪费内存。
> 在新生代会使用该算法。
新生代中分为一个 `Eden` 区和两个 `Survivor` 区。通常两个区域的比例是 `8:1:1` ,使用时会用到 `Eden` 区和其中一个 `Survivor` 区。当发生回收时则会将还存活的对象从 `Eden` ,`Survivor` 区拷贝到另一个 `Survivor` 区,当该区域内存也不足时则会使用分配担保利用老年代来存放内存。
复制算法过程:

### 标记整理算法
复制算法如果在存活对象较多时效率明显会降低,特别是在老年代中并没有多余的内存区域可以提供内存担保。
所以老年代中使用的时候`标记整理算法`,它的原理和`标记清除算法`类似,只是最后一步的清除改为了将存活对象全部移动到一端,然后再将边界之外的内存全部回收。

### 分代回收算法
现代多数的商用 `JVM` 的垃圾收集器都是采用的分代回收算法,和之前所提到的算法并没有新的内容。
只是将 Java 堆分为了新生代和老年代。由于新生代中存活对象较少,所以采用**复制算法**,简单高效。
而老年代中对象较多,并且没有可以担保的内存区域,所以一般采用**标记清除或者是标记整理算法**。
================================================
FILE: MD/HashMap.md
================================================
**更多 HashMap 与 ConcurrentHashMap 相关请查看[这里](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)。**
# HashMap 底层分析
> 以下基于 JDK1.7 分析。

如图所示,HashMap 底层是基于数组和链表实现的。其中有两个重要的参数:
- 容量
- 负载因子
容量的默认大小是 16,负载因子是 0.75,当 `HashMap` 的 `size > 16*0.75` 时就会发生扩容(容量和负载因子都可以自由调整)。
## put 方法
首先会将传入的 Key 做 `hash` 运算计算出 hashcode,然后根据数组长度取模计算出在数组中的 index 下标。
由于在计算中位运算比取模运算效率高的多,所以 HashMap 规定数组的长度为 `2^n` 。这样用 `2^n - 1` 做位运算与取模效果一致,并且效率还要高出许多。
由于数组的长度有限,所以难免会出现不同的 Key 通过运算得到的 index 相同,这种情况可以利用链表来解决,HashMap 会在 `table[index]`处形成链表,采用头插法将数据插入到链表中。
## get 方法
get 和 put 类似,也是将传入的 Key 计算出 index ,如果该位置上是一个链表就需要遍历整个链表,通过 `key.equals(k)` 来找到对应的元素。
## 遍历方式
```java
Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<String, Integer> next = entryIterator.next();
System.out.println("key=" + next.getKey() + " value=" + next.getValue());
}
```
```java
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.println("key=" + key + " value=" + map.get(key));
}
```
```java
map.forEach((key,value)->{
System.out.println("key=" + key + " value=" + value);
});
```
**强烈建议**使用第一种 EntrySet 进行遍历。
第一种可以把 key value 同时取出,第二种还得需要通过 key 取一次 value,效率较低, 第三种需要 `JDK1.8` 以上,通过外层遍历 table,内层遍历链表或红黑树。
## notice
在并发环境下使用 `HashMap` 容易出现死循环。
并发场景发生扩容,调用 `resize()` 方法里的 `rehash()` 时,容易出现环形链表。这样当获取一个不存在的 `key` 时,计算出的 `index` 正好是环形链表的下标时就会出现死循环。

> 所以 HashMap 只能在单线程中使用,并且尽量的预设容量,尽可能的减少扩容。
在 `JDK1.8` 中对 `HashMap` 进行了优化:
当 `hash` 碰撞之后写入链表的长度超过了阈值(默认为8)并且 `table` 的长度不小于64(否则扩容一次)时,链表将会转换为**红黑树**。
假设 `hash` 冲突非常严重,一个数组后面接了很长的链表,此时重新的时间复杂度就是 `O(n)` 。
如果是红黑树,时间复杂度就是 `O(logn)` 。
大大提高了查询效率。
多线程场景下推荐使用 [ConcurrentHashMap](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ConcurrentHashMap.md)。
================================================
FILE: MD/ID-generator.md
================================================
# 分布式 ID 生成器
一个唯一 ID 在一个分布式系统中是非常重要的一个业务属性,其中包括一些如订单 ID,消息 ID ,会话 ID,他们都有一些共有的特性:
- 全局唯一。
- 趋势递增。
全局唯一很好理解,目的就是唯一标识某个次请求,某个业务。
通常有以下几种方案:
## 基于数据库
可以利用 `MySQL` 中的自增属性 `auto_increment` 来生成全局唯一 ID,也能保证趋势递增。
但这种方式太依赖 DB,如果数据库挂了那就非常容易出问题。
### 水平扩展改进
但也有改进空间,可以将数据库水平拆分,如果拆为了两个库 A 库和 B 库。
A 库的递增方式可以是 `0 ,2 ,4 ,6`。B 库则是 `1 ,3 ,5 ,7`。这样的方式可以提高系统可用性,并且 ID 也是趋势递增的。
但也有如下一下问题:
- 想要扩容增加性能变的困难,之前已经定义好了 A B 库递增的步数,新加的数据库不好加入进来,水平扩展困难。
- 也是强依赖与数据库,并且如果其中一台挂掉了那就不是绝对递增了。
## 本地 UUID 生成
还可以采用 `UUID` 的方式生成唯一 ID,由于是在本地生成没有了网络之类的消耗,所有效率非常高。
但也有以下几个问题:
- 生成的 ID 是无序性的,不能做到趋势递增。
- 由于是字符串并且不是递增,所以不太适合用作主键。
## 采用本地时间
这种做法非常简单,可以利用本地的毫秒数加上一些业务 ID 来生成唯一ID,这样可以做到趋势递增,并且是在本地生成效率也很高。
但有一个致命的缺点:当并发量足够高的时候**唯一性**就不能保证了。
## Twitter 雪花算法
可以基于 `Twitter` 的 `Snowflake` 算法来实现。它主要是一种划分命名空间的算法,将生成的 ID 按照机器、时间等来进行标志。
================================================
FILE: MD/Java-lock.md
================================================
# 对锁的一些认知 有哪些锁
## 同一进程
### [重入锁](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ReentrantLock.md)
使用 `ReentrantLock` 获取锁的时候会判断当前线程是否为获取锁的线程,如果是则将同步的状态 +1 ,释放锁的时候则将状态 -1。只有将同步状态的次数置为 0 的时候才会最终释放锁。
### 读写锁
使用 `ReentrantReadWriteLock` ,同时维护一对锁:读锁和写锁。当写线程访问时则其他所有锁都将阻塞,读线程访问时则不会。通过读写锁的分离可以很大程度的提高并发量和吞吐量。
## 不同进程
分布式锁:
### 基于数据库
可以创建一张表,将其中的某个字段设置为`唯一索引`,当多个请求过来的时候只有新建记录成功的请求才算获取到锁,当使用完毕删除这条记录的时候即释放锁。
存在的问题:
- 数据库单点问题,挂了怎么办?
- 不是重入锁,同一进程无法在释放锁之前再次获得锁,因为数据库中已经存在了一条记录了。
- 锁是非阻塞的,一旦 `insert` 失败则会立即返回,并不会进入阻塞队列只能下一次再次获取。
- 锁没有失效时间,如果那个进程解锁失败那就没有请求可以再次获取锁了。
解决方案:
- 数据库切换为主从,不存在单点。
- 在表中加入一个同步状态字段,每次获取锁的是加 1 ,释放锁的时候`-1`,当状态为 0 的时候就删除这条记录,即释放锁。
- 非阻塞的情况可以用 `while` 循环来实现,循环的时候记录时间,达到 X 秒记为超时,`break`。
- 可以开启一个定时任务每隔一段时间扫描找出多少 X 秒都没有被删除的记录,主动删除这条记录。
### 基于 Redis
使用 `setNX(key) setEX(timeout)` 命令,只有在该 `key` 不存在的时候创建这个 `key`,就相当于获取了锁。由于有超时时间,所以过了规定时间会自动删除,这样也可以避免死锁。
可以参考:
[基于 Redis 的分布式锁](http://crossoverjie.top/2018/03/29/distributed-lock/distributed-lock-redis/)
### 基于 ZK
================================================
FILE: MD/Limiting.md
================================================
# 限流算法
限流是解决高并发大流量的一种方案,至少是可以保证应用的可用性。
通常有以下两种限流方案:
- 漏桶算法
- 令牌桶算法
## 漏桶算法

漏桶算法非常简单,就是将流量放入桶中并按照一定的速率流出。如果流量过大时候并不会提高流出效率,而溢出的流量也只能是抛弃掉了。
这种算法很简单,但也非常粗暴,无法应对突发的大流量。
这时可以考虑令牌桶算法。
## 令牌桶算法

令牌桶算法是按照恒定的速率向桶中放入令牌,每当请求经过时则消耗一个或多个令牌。当桶中的令牌为 0 时,请求则会被阻塞。
> note:
令牌桶算法支持先消费后付款,比如一个请求可以获取多个甚至全部的令牌,但是需要后面的请求付费。也就是说后面的请求需要等到桶中的令牌补齐之后才能继续获取。
实例:
```java
@Override
public BaseResponse<UserResVO> getUserByFeignBatch(@RequestBody UserReqVO userReqVO) {
//调用远程服务
OrderNoReqVO vo = new OrderNoReqVO() ;
vo.setReqNo(userReqVO.getReqNo());
RateLimiter limiter = RateLimiter.create(2.0) ;
//批量调用
for (int i = 0 ;i< 10 ; i++){
double acquire = limiter.acquire();
logger.debug("获取令牌成功!,消耗=" + acquire);
BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo);
logger.debug("远程返回:"+JSON.toJSONString(orderNo));
}
UserRes userRes = new UserRes() ;
userRes.setUserId(123);
userRes.setUserName("张三");
userRes.setReqNo(userReqVO.getReqNo());
userRes.setCode(StatusEnum.SUCCESS.getCode());
userRes.setMessage("成功");
return userRes ;
}
```
1. [单 JVM 限流](http://crossoverjie.top/2017/08/11/sbc4/)
2. [分布式限流](http://crossoverjie.top/2018/04/28/sbc/sbc7-Distributed-Limit/)
================================================
FILE: MD/LinkedList.md
================================================
# LinkedList 底层分析

如图所示 `LinkedList` 底层是基于双向链表实现的,也是实现了 `List` 接口,所以也拥有 List 的一些特点(JDK1.7/8 之后取消了循环,修改为双向链表)。
## 新增方法
```java
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
```
可见每次插入都是移动指针,和 ArrayList 的拷贝数组来说效率要高上不少。
## 查询方法
```java
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
```
上述代码,利用了双向链表的特性,如果`index`离链表头比较近,就从节点头部遍历。否则就从节点尾部开始遍历。使用空间(双向链表)来换取时间。
- `node()`会以`O(n/2)`的性能去获取一个结点
- 如果索引值大于链表大小的一半,那么将从尾结点开始遍历
这样的效率是非常低的,特别是当 index 越接近 size 的中间值时。
总结:
- LinkedList 插入,删除都是移动指针效率很高。
- 查找需要进行遍历查询,效率较低。
================================================
FILE: MD/MemoryAllocation.md
================================================
# Java 运行时的内存划分

## 程序计数器
记录当前线程所执行的字节码行号,用于获取下一条执行的字节码。
当多线程运行时,每个线程切换后需要知道上一次所运行的状态、位置。由此也可以看出程序计数器是每个线程**私有**的。
## 虚拟机栈
虚拟机栈由一个一个的栈帧组成,栈帧是在每一个方法调用时产生的。
每一个栈帧由`局部变量区`、`操作数栈`等组成。每创建一个栈帧压栈,当一个方法执行完毕之后则出栈。
> - 如果出现方法递归调用出现死循环的话就会造成栈帧过多,最终会抛出 `StackOverflowError`。
> - 若线程执行过程中栈帧大小超出虚拟机栈限制,则会抛出 `StackOverflowError`。
> - 若虚拟机栈允许动态扩展,但在尝试扩展时内存不足,或者在为一个新线程初始化新的虚拟机栈时申请不到足够的内存,则会抛出
`OutOfMemoryError`。
**这块内存区域也是线程私有的。**
## Java 堆
`Java` 堆是整个虚拟机所管理的最大内存区域,所有的对象创建都是在这个区域进行内存分配。
可利用参数 `-Xms -Xmx` 进行堆内存控制。
这块区域也是垃圾回收器重点管理的区域,由于大多数垃圾回收器都采用`分代回收算法`,所有堆内存也分为 `新生代`、`老年代`,可以方便垃圾的准确回收。
**这块内存属于线程共享区域。**
## 方法区(JDK1.7)
方法区主要用于存放已经被虚拟机加载的类信息,如`常量,静态变量`。
这块区域也被称为`永久代`。
可利用参数 `-XX:PermSize -XX:MaxPermSize` 控制初始化方法区和最大方法区大小。
## 元数据区(JDK1.8)
在 `JDK1.8` 中已经移除了方法区(永久代),并使用了一个元数据区域进行代替(`Metaspace`)。
默认情况下元数据区域会根据使用情况动态调整,避免了在 1.7 中由于加载类过多从而出现 `java.lang.OutOfMemoryError: PermGen`。
但也不能无限扩展,因此可以使用 `-XX:MaxMetaspaceSize`来控制最大内存。
## 运行时常量池
运行时常量池是方法区的一部分,其中存放了一些符号引用。当 `new` 一个对象时,会检查这个区域是否有这个符号的引用。
## 直接内存
直接内存又称为 `Direct Memory(堆外内存)`,它并不是由 `JVM` 虚拟机所管理的一块内存区域。
有使用过 `Netty` 的朋友应该对这块并内存不陌生,在 `Netty` 中所有的 IO(nio) 操作都会通过 `Native` 函数直接分配堆外内存。
它是通过在堆内存中的 `DirectByteBuffer` 对象操作的堆外内存,避免了堆内存和堆外内存来回复制交换复制,这样的高效操作也称为`零拷贝`。
既然是内存,那也得是可以被回收的。但由于堆外内存不直接受 `JVM` 管理,所以常规 `GC` 操作并不能回收堆外内存。它是借助于老年代产生的 `fullGC` 顺便进行回收。同时也可以显式调用 `System.gc()` 方法进行回收(前提是没有使用 `-XX:+DisableExplicitGC` 参数来禁止该方法)。
**值得注意的是**:由于堆外内存也是内存,是由操作系统管理。如果应用有使用堆外内存则需要平衡虚拟机的堆内存和堆外内存的使用占比。避免出现堆外内存溢出。
## 常用参数

通过上图可以直观的查看各个区域的参数设置。
常见的如下:
- `-Xms64m` 最小堆内存 `64m`.
- `-Xmx128m` 最大堆内存 `128m`.
- `-XX:NewSize=30m` 新生代初始化大小为`30m`.
- `-XX:MaxNewSize=40m` 新生代最大大小为`40m`.
- `-Xss=256k` 线程栈大小。
- `-XX:+PrintHeapAtGC` 当发生 GC 时打印内存布局。
- `-XX:+HeapDumpOnOutOfMemoryError` 发送内存溢出时 dump 内存。
新生代和老年代的默认比例为 `1:2`,也就是说新生代占用 `1/3`的堆内存,而老年代占用 `2/3` 的堆内存。
可以通过参数 `-XX:NewRatio=2` 来设置老年代/新生代的比例。
================================================
FILE: MD/MySQL-Index.md
================================================
# MySQL 索引原理
现在互联网应用中对数据库的使用多数都是读较多,比例可以达到 `10:1`。并且数据库在做查询时 `IO` 消耗较大,所以如果能把一次查询的 `IO` 次数控制在常量级那对数据库的性能提升将是非常明显的,因此基于 `B+ Tree` 的索引结构出现了。
## B+ Tree 的数据结构

如图所示是 `B+ Tree` 的数据结构。是由一个一个的磁盘块组成的树形结构,每个磁盘块由数据项和指针组成。
> 所有的数据都是存放在叶子节点,非叶子节点不存放数据。
## 查找过程
以磁盘块1为例,指针 P1 表示小于17的磁盘块,P2 表示在 `17~35` 之间的磁盘块,P3 则表示大于35的磁盘块。
比如要查找数据项99,首先将磁盘块1 load 到内存中,发生 1 次 `IO`。接着通过二分查找发现 99 大于 35,所以找到了 P3 指针。通过P3 指针发生第二次 IO 将磁盘块4加载到内存。再通过二分查找发现大于87,通过 P3 指针发生了第三次 IO 将磁盘块11 加载到内存。最后再通过一次二分查找找到了数据项99。
由此可见,如果一个几百万的数据查询只需要进行三次 IO 即可找到数据,那么整个效率将是非常高的。
观察树的结构,发现查询需要经历几次 IO 是由树的高度来决定的,而树的高度又由磁盘块,数据项的大小决定的。
磁盘块越大,数据项越小那么树的高度就越低。这也就是为什么索引字段要尽可能小的原因。
> 索引使用的一些[原则](https://github.com/crossoverJie/Java-Interview/blob/master/MD/SQL-optimization.md)。
================================================
FILE: MD/OOM-analysis.md
================================================
# OOM 分析
## Java 堆内存溢出
在 Java 堆中只要不断的创建对象,并且 `GC-Roots` 到对象之间存在引用链,这样 `JVM` 就不会回收对象。
只要将`-Xms(最小堆)`,`-Xmx(最大堆)` 设置为一样禁止自动扩展堆内存。
当使用一个 `while(true)` 循环来不断创建对象就会发生 `OutOfMemory`,还可以使用 `-XX:+HeapDumpOnOutOfMemoryError` 当发生 OOM 时会自动 dump 堆栈到文件中。
伪代码:
```java
public static void main(String[] args) {
List<String> list = new ArrayList<>(10) ;
while (true){
list.add("1") ;
}
}
```
当出现 OOM 时可以通过工具来分析 `GC-Roots` [引用链](https://github.com/crossoverJie/Java-Interview/blob/master/MD/GarbageCollection.md#%E5%8F%AF%E8%BE%BE%E6%80%A7%E5%88%86%E6%9E%90%E7%AE%97%E6%B3%95) ,查看对象和 `GC-Roots` 是如何进行关联的,是否存在对象的生命周期过长,或者是这些对象确实改存在的,那就要考虑将堆内存调大了。
```
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at com.crossoverjie.oom.HeapOOM.main(HeapOOM.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Process finished with exit code 1
```
`java.lang.OutOfMemoryError: Java heap space`表示堆内存溢出。
更多内存溢出相关实战请看这里:[强如 Disruptor 也发生内存溢出?](https://crossoverjie.top/2018/08/29/java-senior/OOM-Disruptor/)
## MetaSpace (元数据) 内存溢出
> `JDK8` 中将永久代移除,使用 `MetaSpace` 来保存类加载之后的类信息,字符串常量池也被移动到 Java 堆。
`PermSize` 和 `MaxPermSize` 已经不能使用了,在 JDK8 中配置这两个参数将会发出警告。
JDK 8 中将类信息移到到了本地堆内存(Native Heap)中,将原有的永久代移动到了本地堆中成为 `MetaSpace` ,如果不指定该区域的大小,JVM 将会动态的调整。
可以使用 `-XX:MaxMetaspaceSize=10M` 来限制最大元数据。这样当不停的创建类时将会占满该区域并出现 `OOM`。
```java
public static void main(String[] args) {
while (true){
Enhancer enhancer = new Enhancer() ;
enhancer.setSuperclass(HeapOOM.class);
enhancer.setUseCache(false) ;
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(o,objects) ;
}
});
enhancer.create() ;
}
}
```
使用 `cglib` 不停的创建新类,最终会抛出:
```
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
... 11 more
Caused by: java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
... 16 more
```
注意:这里的 OOM 伴随的是 `java.lang.OutOfMemoryError: Metaspace` 也就是元数据溢出。
================================================
FILE: MD/ReentrantLock.md
================================================
# ReentrantLock 实现原理
使用 `synchronized` 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。
而 `ReentrantLock` 就是一个普通的类,它是基于 `AQS(AbstractQueuedSynchronizer)`来实现的。
是一个**重入锁**:一个线程获得了锁之后仍然可以**反复**的加锁,不会出现自己阻塞自己的情况。
> `AQS` 是 `Java` 并发包里实现锁、同步的一个重要的基础框架。
## 锁类型
ReentrantLock 分为**公平锁**和**非公平锁**,可以通过构造方法来指定具体类型:
```java
//默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
```
默认一般使用**非公平锁**,它的效率和吞吐量都比公平锁高的多(后面会分析具体原因)。
## 获取锁
通常的使用方式如下:
```java
private ReentrantLock lock = new ReentrantLock();
public void run() {
lock.lock();
try {
//do bussiness
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
```
### 公平锁获取锁
首先看下获取锁的过程:
```java
public void lock() {
sync.lock();
}
```
可以看到是使用 `sync`的方法,而这个方法是一个抽象方法,具体是由其子类(`FairSync`)来实现的,以下是公平锁的实现:
```java
final void lock() {
acquire(1);
}
//AbstractQueuedSynchronizer 中的 acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
```
第一步是尝试获取锁(`tryAcquire(arg)`),这个也是由其子类实现:
```java
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
```
首先会判断 `AQS` 中的 `state` 是否等于 0,0 表示目前没有其他线程获得锁,当前线程就可以尝试获取锁。
**注意**:尝试之前会利用 `hasQueuedPredecessors()` 方法来判断 AQS 的队列中中是否有其他线程,如果有则不会尝试获取锁(**这是公平锁特有的情况**)。
如果队列中没有线程就利用 CAS 来将 AQS 中的 state 修改为1,也就是获取锁,获取成功则将当前线程置为获得锁的独占线程(`setExclusiveOwnerThread(current)`)。
如果 `state` 大于 0 时,说明锁已经被获取了,则需要判断获取锁的线程是否为当前线程(`ReentrantLock` 支持重入),是则需要将 `state + 1`,并将值更新。
#### 写入队列
如果 `tryAcquire(arg)` 获取锁失败,则需要用 `addWaiter(Node.EXCLUSIVE)` 将当前线程写入队列中。
写入之前需要将当前线程包装为一个 `Node` 对象(`addWaiter(Node.EXCLUSIVE)`)。
> AQS 中的队列是由 Node 节点组成的双向链表实现的。
包装代码:
```java
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
```
首先判断队列是否为空,不为空时则将封装好的 `Node` 利用 `CAS` 写入队尾,如果出现并发写入失败就需要调用 `enq(node);` 来写入了。
```java
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
```
这个处理逻辑就相当于`自旋`加上 `CAS` 保证一定能写入队列。
#### 挂起等待线程
写入队列之后需要将当前线程挂起(利用`acquireQueued(addWaiter(Node.EXCLUSIVE), arg)`):
```java
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
```
首先会根据 `node.predecessor()` 获取到上一个节点是否为头节点,如果是则尝试获取一次锁,获取成功就万事大吉了。
如果不是头节点,或者获取锁失败,则会根据上一个节点的 `waitStatus` 状态来处理(`shouldParkAfterFailedAcquire(p, node)`)。
`waitStatus` 用于记录当前节点的状态,如节点取消、节点等待等。
`shouldParkAfterFailedAcquire(p, node)` 返回当前线程是否需要挂起,如果需要则调用 `parkAndCheckInterrupt()`:
```java
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
```
他是利用 `LockSupport` 的 `part` 方法来挂起当前线程的,直到被唤醒。
### 非公平锁获取锁
公平锁与非公平锁的差异主要在获取锁:
公平锁就相当于买票,后来的人需要排到队尾依次买票,**不能插队**。
而非公平锁则没有这些规则,是**抢占模式**,每来一个人不会去管队列如何,直接尝试获取锁。
非公平锁:
```java
final void lock() {
//直接尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
```
公平锁:
```java
final void lock() {
acquire(1);
}
```
还要一个重要的区别是在尝试获取锁时`tryAcquire(arg)`,非公平锁是不需要判断队列中是否还有其他线程,也是直接尝试获取锁:
```java
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//没有 !hasQueuedPredecessors() 判断
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
```
## 释放锁
公平锁和非公平锁的释放流程都是一样的:
```java
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒被挂起的线程
unparkSuccessor(h);
return true;
}
return false;
}
//尝试释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
```
首先会判断当前线程是否为获得锁的线程,由于是重入锁所以需要将 `state` 减到 0 才认为完全释放锁。
释放之后需要调用 `unparkSuccessor(h)` 来唤醒被挂起的线程。
## 总结
由于公平锁需要关心队列的情况,得按照队列里的先后顺序来获取锁(会造成大量的线程上下文切换),而非公平锁则没有这个限制。
所以也就能解释非公平锁的效率会被公平锁更高。
================================================
FILE: MD/SQL-optimization.md
================================================
# SQL 优化
### 负向查询不能使用索引
```sql
select name from user where id not in (1,3,4);
```
应该修改为:
```
select name from user where id in (2,5,6);
```
### 前导模糊查询不能使用索引
如:
```sql
select name from user where name like '%zhangsan'
```
非前导则可以:
```sql
select name from user where name like 'zhangsan%'
```
建议可以考虑使用 `Lucene` 等全文索引工具来代替频繁的模糊查询。
### 数据区分不明显的不建议创建索引
如 user 表中的性别字段,可以明显区分的才建议创建索引,如身份证等字段。
### 字段的默认值不要为 null
这样会带来和预期不一致的查询结果。
### 在字段上进行计算不能命中索引
```sql
select name from user where FROM_UNIXTIME(create_time) < CURDATE();
```
应该修改为:
```sql
select name from user where create_time < FROM_UNIXTIME(CURDATE());
```
### 最左前缀问题
如果给 user 表中的 username pwd 字段创建了复合索引那么使用以下SQL 都是可以命中索引:
```sql
select username from user where username='zhangsan' and pwd ='axsedf1sd'
select username from user where pwd ='axsedf1sd' and username='zhangsan'
select username from user where username='zhangsan'
```
但是使用
```sql
select username from user where pwd ='axsedf1sd'
```
是不能命中索引的。
### 如果明确知道只有一条记录返回
```sql
select name from user where username='zhangsan' limit 1
```
可以提高效率,可以让数据库停止游标移动。
### 不要让数据库帮我们做强制类型转换
```sql
select name from user where telno=18722222222
```
这样虽然可以查出数据,但是会导致全表扫描。
需要修改为
```
select name from user where telno='18722222222'
```
### 如果需要进行 join 的字段两表的字段类型要相同
不然也不会命中索引。
================================================
FILE: MD/Spike.md
================================================
# 设计一个秒杀系统
**具体实现参考 [秒杀架构实践](https://crossoverjie.top/2018/05/07/ssm/SSM18-seconds-kill/)**
主要做到以下两点:
- 尽量将请求过滤在上游。
- 尽可能的利用缓存(大多数场景下都是**查多于写**)。
常用的系统分层结构:
<div align="center"> <img src="https://ws4.sinaimg.cn/large/006tNc79ly1fmjw06nz2zj306f0fejrh.jpg" width=""/> </div><br>
针对于浏览器端,可以使用 JS 进行请求过滤,比如五秒钟之类只能点一次抢购按钮,五秒钟只能允许请求一次后端服务。(APP 同理)
这样其实就可以过滤掉大部分普通用户。
但是防不住直接抓包循环调用。这种情况可以最简单的处理:在`Web层`通过限制一个 UID 五秒之类的请求服务层的次数(可利用 Redis 实现)。
但如果是真的有 10W 个不同的 UID 来请求,比如黑客抓肉鸡的方式。
这种情况可以在`服务层` 针对于写请求使用请求队列,再通过限流算法([限流算法](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Limiting.md))每秒钟放一部分请求到队列。
对于读请求则尽量使用缓存,可以提前将数据准备好,不管是 `Redis` 还是其他缓存中间件效率都是非常高的。
> ps : 刷新缓存情况,比如库存扣除成功这种情况不用马上刷新缓存,如果库存扣到了 0 再刷新缓存。因为大多数用户都只关心是否有货,并不关心现在还剩余多少。
## 总结
- 如果流量巨大,导致各个层的压力都很大可以适当的加机器横向扩容。如果加不了机器那就只有放弃流量直接返回失败。快速失败非常重要,至少可以保证系统的可用性。
- 业务分批执行:对于下单、付款等操作可以异步执行提高吞吐率。
- 主要目的就是尽量少的请求直接访问到 `DB`。
================================================
FILE: MD/SpringAOP.md
================================================
# Spring AOP 实现原理
## 静态代理
众所周知 Spring 的 `AOP` 是基于动态代理实现的,谈到动态代理就不得不提下静态代理。实现如下:
假设有一接口 `InterfaceA`:
```java
public interface InterfaceA{
void exec();
}
```
其中有实现类 `RealImplement`:
```java
public class RealImplement implement InterfaceA{
public void exec(){
System.out.println("real impl") ;
}
}
```
这时也有一个代理类 `ProxyImplement` 也实现了 `InterfaceA`:
```java
public class ProxyImplement implement InterfaceA{
private InterfaceA interface ;
public ProxyImplement(){
interface = new RealImplement() ;
}
public void exec(){
System.out.println("dosomethings before);
//实际调用
interface.exec();
System.out.println("dosomethings after);
}
}
```
使用如下:
```java
public class Main(){
public static void main(String[] args){
InterfaceA interface = new ProxyImplement() ;
interface.exec();
}
}
```
可以看出这样的代理方式调用者其实都不知道被代理对象的存在。
## JDK 动态代理
从静态代理中可以看出: 静态代理只能代理一个具体的类,如果要代理一个接口的多个实现的话需要定义不同的代理类。
需要解决这个问题就可以用到 JDK 的动态代理。
其中有两个非常核心的类:
- `java.lang.reflect.Proxy`类。
- `java.lang.reflect.InvocationHandler`接口。
`Proxy` 类是用于创建代理对象,而 `InvocationHandler` 接口主要你是来处理执行逻辑。
如下:
```java
public class CustomizeHandle implements InvocationHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(CustomizeHandle.class);
private Object target;
public CustomizeHandle(Class clazz) {
try {
this.target = clazz.newInstance();
} catch (InstantiationException e) {
LOGGER.error("InstantiationException", e);
} catch (IllegalAccessException e) {
LOGGER.error("IllegalAccessException",e);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
LOGGER.info("proxy class={}", proxy.getClass());
return result;
}
private void before() {
LOGGER.info("handle before");
}
private void after() {
LOGGER.info("handle after");
}
}
```
其中构造方法传入被代理类的类类型。其实传代理类的实例或者是类类型并没有强制的规定,传类类型的是因为被代理对象应当由代理创建而不应该由调用方创建。
使用方式如下:
```java
@Test
public void test(){
CustomizeHandle handle = new CustomizeHandle(ISubjectImpl.class) ;
ISubject subject = (ISubject) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{ISubject.class}, handle);
subject.execute() ;
}
```
首先传入被代理类的类类型构建代理处理器。接着使用 `Proxy` 的`newProxyInstance` 方法动态创建代理类。第一个参数为类加载器,第二个参数为代理类需要实现的接口列表,最后一个则是处理器。
其实代理类是由

这个方法动态创建出来的。将 proxyClassFile 输出到文件并进行反编译的话就可以的到代理类。
```java
@Test
public void clazzTest(){
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
"$Proxy1", new Class[]{ISubject.class}, 1);
try {
FileOutputStream out = new FileOutputStream("/Users/chenjie/Documents/$Proxy1.class") ;
out.write(proxyClassFile);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
```
反编译后结果如下:
```java
import com.crossoverjie.proxy.jdk.ISubject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public class $Proxy1 extends Proxy implements ISubject {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void execute() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.crossoverjie.proxy.jdk.ISubject").getMethod("execute", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
```
可以看到代理类继承了 `Proxy` 类,并实现了 `ISubject` 接口,由此也可以看到 JDK 动态代理为什么需要实现接口,已经继承了 `Proxy`是不能再继承其余类了。
其中实现了 `ISubject` 的 `execute()` 方法,并通过 `InvocationHandler` 中的 `invoke()` 方法来进行调用的。
## CGLIB 动态代理
cglib 是对一个小而快的字节码处理框架 `ASM` 的封装。
他的特点是继承于被代理类,这就要求被代理类不能被 `final` 修饰。
================================================
FILE: MD/Synchronize.md
================================================
# synchronized 关键字原理
众所周知 `synchronized` 关键字是解决并发问题常用解决方案,有以下三种使用方式:
- 同步普通方法,锁的是当前对象。
- 同步静态方法,锁的是当前 `Class` 对象。
- 同步块,锁的是 `()` 中的对象。
实现原理:
`JVM` 是通过进入、退出对象监视器( `Monitor` )来实现对方法、同步块的同步的。
具体实现是在编译之后在同步方法调用前加入一个 `monitor.enter` 指令,在退出方法和异常处插入 `monitor.exit` 的指令。
其本质就是对一个对象监视器( `Monitor` )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 `monitor.exit` 之后才能尝试继续获取锁。
流程图如下:

通过一段代码来演示:
```java
public static void main(String[] args) {
synchronized (Synchronize.class){
System.out.println("Synchronize");
}
}
```
使用 `javap -c Synchronize` 可以查看编译之后的具体信息。
```
public class com.crossoverjie.synchronize.Synchronize {
public com.crossoverjie.synchronize.Synchronize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // class com/crossoverjie/synchronize/Synchronize
2: dup
3: astore_1
**4: monitorenter**
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String Synchronize
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
**14: monitorexit**
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
}
```
可以看到在同步块的入口和出口分别有 `monitorenter,monitorexit`
指令。
## 锁优化
`synchronized` 很多都称之为重量锁,`JDK1.6` 中对 `synchronized` 进行了各种优化,为了能减少获取和释放锁带来的消耗引入了`偏向锁`和`轻量锁`。
### 轻量锁
当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(`Lock Record`)区域,同时将锁对象的对象头中 `Mark Word` 拷贝到锁记录中,再尝试使用 `CAS` 将 `Mark Word` 更新为指向锁记录的指针。
如果更新**成功**,当前线程就获得了锁。
如果更新**失败** `JVM` 会先检查锁对象的 `Mark Word` 是否指向当前线程的锁记录。
如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。
不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,**轻量锁就会膨胀为重量锁**。
#### 解锁
轻量锁的解锁过程也是利用 `CAS` 来实现的,会尝试锁记录替换回锁对象的 `Mark Word` 。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为`重量锁`)
轻量锁能提升性能的原因是:
认为大多数锁在整个同步周期都不存在竞争,所以使用 `CAS` 比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 `CAS` 的开销,甚至比重量锁更慢。
### 偏向锁
为了进一步的降低获取锁的代价,`JDK1.6` 之后还引入了偏向锁。
偏向锁的特征是:锁不存在多线程竞争,并且应由一个线程多次获得锁。
当线程访问同步块时,会使用 `CAS` 将线程 ID 更新到锁对象的 `Mark Word` 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
#### 释放锁
当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 `Mark Word` 设置为无锁或者是轻量锁状态。
偏向锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用 `-XX:-UseBiasedLocking` 来关闭偏向锁,并默认进入轻量锁。
### 其他优化
#### 适应性自旋
在使用 `CAS` 时,如果操作失败,`CAS` 会自旋再次尝试。由于自旋是需要消耗 `CPU` 资源的,所以如果长期自旋就白白浪费了 `CPU`。`JDK1.6`加入了适应性自旋:
> 如果某个锁自旋很少成功获得,那么下一次就会减少自旋。
================================================
FILE: MD/TCP-IP.md
================================================
# TCP/IP 协议
`TCP/IP` 总结起来就三个要点
- 三次握手的意义。
- 超时重发。
- 滑动窗口。
## 三次握手

如图类似:
1. 发送者问接收者我发消息了,你收到了嘛?
2. 接收者回复发送者我收到了,你发消息没问题,我收消息也没问题。但我不知道我的发消息有没有问题,你收到了回复我下。
3. 发送者告诉接收者,我收到你的消息了,你发消息没问题。通信成功我们开始工作吧!
## 超时重发
当发送者向接收者发包后,如果过了一段时间(超时时间)依然没有收到消息,就当做本次包丢失,需要重新补发。
并且如果一次性发了三个包,只要最后一个包确认收到之后就默认前面两个也收到了。
## 滑动窗口
假设一次性发送包的大小为3,那么每次可以发3个包,而且可以边发边接收,这样就会增强效率。这里的 3 就是滑动窗口的大小,这样的发送方式也叫滑动窗口协议。
================================================
FILE: MD/Thread-common-problem.md
================================================
# Java 多线程常见问题
## 上下文切换
多线程并不一定是要在多核处理器才支持的,就算是单核也是可以支持多线程的。
CPU 通过给每个线程分配一定的时间片,由于时间非常短通常是几十毫秒,所以 CPU 可以不停的切换线程执行任务从而达到了多线程的效果。
但是由于在线程切换的时候需要保存本次执行的信息([详见](https://github.com/crossoverJie/Java-Interview/blob/master/MD/MemoryAllocation.md#%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8)),在该线程被 CPU 剥夺时间片后又再次运行恢复上次所保存的信息的过程就称为上下文切换。
> 上下文切换是非常耗效率的。
通常有以下解决方案:
- 采用无锁编程,比如将数据按照 `Hash(id)` 进行取模分段,每个线程处理各自分段的数据,从而避免使用锁。
- 采用 CAS(compare and swap) 算法,如 `Atomic` 包就是采用 CAS 算法([详见](https://github.com/crossoverJie/JCSprout/blob/master/MD/Threadcore.md#%E5%8E%9F%E5%AD%90%E6%80%A7))。
- 合理的创建线程,避免创建了一些线程但其中大部分都是处于 `waiting` 状态,因为每当从 `waiting` 状态切换到 `running` 状态都是一次上下文切换。
## 死锁
死锁的场景一般是:线程 A 和线程 B 都在互相等待对方释放锁,或者是其中某个线程在释放锁的时候出现异常如死循环之类的。这时就会导致系统不可用。
常用的解决方案如下:
- 尽量一个线程只获取一个锁。
- 一个线程只占用一个资源。
- 尝试使用定时锁,至少能保证锁最终会被释放。
## 资源限制
当在带宽有限的情况下一个线程下载某个资源需要 `1M/S`,当开 10 个线程时速度并不会乘 10 倍,反而还会增加时间,毕竟上下文切换比较耗时。
如果是受限于资源的话可以采用集群来处理任务,不同的机器来处理不同的数据,就类似于开始提到的无锁编程。
================================================
FILE: MD/ThreadPoolExecutor.md
================================================
## 前言
平时接触过多线程开发的童鞋应该都或多或少了解过线程池,之前发布的《阿里巴巴 Java 手册》里也有一条:

可见线程池的重要性。
简单来说使用线程池有以下几个目的:
- 线程是稀缺资源,不能频繁的创建。
- 解耦作用;线程的创建于执行完全分开,方便维护。
- 应当将其放入一个池子中,可以给其他任务进行复用。
## 线程池原理
谈到线程池就会想到池化技术,其中最核心的思想就是把宝贵的资源放到一个池子中;每次使用都从里面获取,用完之后又放回池子供其他人使用,有点吃大锅饭的意思。
那在 Java 中又是如何实现的呢?
在 JDK 1.5 之后推出了相关的 api,常见的创建线程池方式有以下几种:
- `Executors.newCachedThreadPool()`:无限线程池。
- `Executors.newFixedThreadPool(nThreads)`:创建固定大小的线程池。
- `Executors.newSingleThreadExecutor()`:创建单个线程的线程池。
其实看这三种方式创建的源码就会发现:
```java
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
```
实际上还是利用 `ThreadPoolExecutor` 类实现的。
所以我们重点来看下 `ThreadPoolExecutor` 是怎么玩的。
首先是创建线程的 api:
```java
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
```
这几个核心参数的作用:
- `corePoolSize` 为线程池的基本大小。
- `maximumPoolSize` 为线程池最大线程大小。
- `keepAliveTime` 和 `unit` 则是线程空闲后的存活时间。
- `workQueue` 用于存放任务的阻塞队列。
- `handler` 当队列和最大线程池都满了之后的饱和策略。
了解了这几个参数再来看看实际的运用。
通常我们都是使用:
```java
threadPool.execute(new Job());
```
这样的方式来提交一个任务到线程池中,所以核心的逻辑就是 `execute()` 函数了。
在具体分析之前先了解下线程池中所定义的状态,这些状态都和线程的执行密切相关:

- `RUNNING` 自然是运行状态,指可以接受任务执行队列里的任务
- `SHUTDOWN` 指调用了 `shutdown()` 方法,不再接受新任务了,但是队列里的任务得执行完毕。
- `STOP` 指调用了 `shutdownNow()` 方法,不再接受新任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务。
- `TIDYING` 所有任务都执行完毕,在调用 `shutdown()/shutdownNow()` 中都会尝试更新为这个状态。
- `TERMINATED` 终止状态,当执行 `terminated()` 后会更新为这个状态。
用图表示为:

然后看看 `execute()` 方法是如何处理的:

1. 获取当前线程池的状态。
2. 当前线程数量小于 coreSize 时创建一个新的线程运行。
3. 如果当前线程处于运行状态,并且写入阻塞队列成功。
4. 双重检查,再次获取线程池状态;如果线程池状态变了(非运行状态)就需要从阻塞队列移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
5. 如果当前线程池为空就新创建一个线程并执行。
6. 如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行拒绝策略。
这里借助《聊聊并发》的一张图来描述这个流程:

### 如何配置线程
流程聊完了再来看看上文提到了几个核心参数应该如何配置呢?
有一点是肯定的,线程池肯定是不是越大越好。
通常我们是需要根据这批任务执行的性质来确定的。
- IO 密集型任务:由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 CPU 个数 * 2
- CPU 密集型任务(大量复杂的运算)应当分配较少的线程,比如 CPU 个数相当的大小。
当然这些都是经验值,最好的方式还是根据实际情况测试得出最佳配置。
### 优雅的关闭线程池
有运行任务自然也有关闭任务,从上文提到的 5 个状态就能看出如何来关闭线程池。
其实无非就是两个方法 `shutdown()/shutdownNow()`。
但他们有着重要的区别:
- `shutdown()` 执行后停止接受新任务,会把队列的任务执行完毕。
- `shutdownNow()` 也是停止接受新任务,但会中断所有的任务,将线程池状态变为 stop。
> 两个方法都会中断线程,用户可自行判断是否需要响应中断。
`shutdownNow()` 要更简单粗暴,可以根据实际场景选择不同的方法。
我通常是按照以下方式关闭线程池的:
```java
long start = System.currentTimeMillis();
for (int i = 0; i <= 5; i++) {
pool.execute(new Job());
}
pool.shutdown();
while (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
LOGGER.info("线程还在执行。。。");
}
long end = System.currentTimeMillis();
LOGGER.info("一共处理了【{}】", (end - start));
```
`pool.awaitTermination(1, TimeUnit.SECONDS)` 会每隔一秒钟检查一次是否执行完毕(状态为 `TERMINATED`),当从 while 循环退出时就表明线程池已经完全终止了。
## SpringBoot 使用线程池
2018 年了,SpringBoot 盛行;来看看在 SpringBoot 中应当怎么配置和使用线程池。
既然用了 SpringBoot ,那自然得发挥 Spring 的特性,所以需要 Spring 来帮我们管理线程池:
```java
@Configuration
public class TreadPoolConfig {
/**
* 消费队列线程
* @return
*/
@Bean(value = "consumerQueueThreadPool")
public ExecutorService buildConsumerQueueThreadPool(){
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("consumer-queue-thread-%d").build();
ExecutorService pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
return pool ;
}
}
```
使用时:
```java
@Resource(name = "consumerQueueThreadPool")
private ExecutorService consumerQueueThreadPool;
@Override
public void execute() {
//消费队列
for (int i = 0; i < 5; i++) {
consumerQueueThreadPool.execute(new ConsumerQueueThread());
}
}
```
其实也挺简单,就是创建了一个线程池的 bean,在使用时直接从 Spring 中取出即可。
## 监控线程池
谈到了 SpringBoot,也可利用它 actuator 组件来做线程池的监控。
线程怎么说都是稀缺资源,对线程池的监控可以知道自己任务执行的状况、效率等。
关于 actuator 就不再细说了,感兴趣的可以看看[这篇](http://t.cn/ReimM0o),有详细整理过如何暴露监控端点。
其实 ThreadPool 本身已经提供了不少 api 可以获取线程状态:

很多方法看名字就知道其含义,只需要将这些信息暴露到 SpringBoot 的监控端点中,我们就可以在可视化页面查看当前的线程池状态了。
甚至我们可以继承线程池扩展其中的几个函数来自定义监控逻辑:


看这些名称和定义都知道,这是让子类来实现的。
可以在线程执行前、后、终止状态执行自定义逻辑。
## 线程池隔离
> 线程池看似很美好,但也会带来一些问题。
如果我们很多业务都依赖于同一个线程池,当其中一个业务因为各种不可控的原因消耗了所有的线程,导致线程池全部占满。
这样其他的业务也就不能正常运转了,这对系统的打击是巨大的。
比如我们 Tomcat 接受请求的线程池,假设其中一些响应特别慢,线程资源得不到回收释放;线程池慢慢被占满,最坏的情况就是整个应用都不能提供服务。
所以我们需要将线程池**进行隔离**。
通常的做法是按照业务进行划分:
> 比如下单的任务用一个线程池,获取数据的任务用另一个线程池。这样即使其中一个出现问题把线程池耗尽,那也不会影响其他的任务运行。
### hystrix 隔离
这样的需求 [Hystrix](https://github.com/Netflix/Hystrix) 已经帮我们实现了。
> Hystrix 是一款开源的容错插件,具有依赖隔离、系统容错降级等功能。
下面来看看 `Hystrix` 简单的应用:
首先需要定义两个线程池,分别用于执行订单、处理用户。
```java
/**
* Function:订单服务
*
* @author crossoverJie
* Date: 2018/7/28 16:43
* @since JDK 1.8
*/
public class CommandOrder extends HystrixCommand<String> {
private final static Logger LOGGER = LoggerFactory.getLogger(CommandOrder.class);
private String orderName;
public CommandOrder(String orderName) {
super(Setter.withGroupKey(
//服务分组
HystrixCommandGroupKey.Factory.asKey("OrderGroup"))
//线程分组
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("OrderPool"))
//线程池配置
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(10)
.withKeepAliveTimeMinutes(5)
.withMaxQueueSize(10)
.withQueueSizeRejectionThreshold(10000))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
)
;
this.orderName = orderName;
}
@Override
public String run() throws Exception {
LOGGER.info("orderName=[{}]", orderName);
TimeUnit.MILLISECONDS.sleep(100);
return "OrderName=" + orderName;
}
}
/**
* Function:用户服务
*
* @author crossoverJie
* Date: 2018/7/28 16:43
* @since JDK 1.8
*/
public class CommandUser extends HystrixCommand<String> {
private final static Logger LOGGER = LoggerFactory.getLogger(CommandUser.class);
private String userName;
public CommandUser(String userName) {
super(Setter.withGroupKey(
//服务分组
HystrixCommandGroupKey.Factory.asKey("UserGroup"))
//线程分组
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("UserPool"))
//线程池配置
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(10)
.withKeepAliveTimeMinutes(5)
.withMaxQueueSize(10)
.withQueueSizeRejectionThreshold(10000))
//线程池隔离
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
)
;
this.userName = userName;
}
@Override
public String run() throws Exception {
LOGGER.info("userName=[{}]", userName);
TimeUnit.MILLISECONDS.sleep(100);
return "userName=" + userName;
}
}
```
-----
`api` 特别简洁易懂,具体详情请查看官方文档。
然后模拟运行:
```java
public static void main(String[] args) throws Exception {
CommandOrder commandPhone = new CommandOrder("手机");
CommandOrder command = new CommandOrder("电视");
//阻塞方式执行
String execute = commandPhone.execute();
LOGGER.info("execute=[{}]", execute);
//异步非阻塞方式
Future<String> queue = command.queue();
String value = queue.get(200, TimeUnit.MILLISECONDS);
LOGGER.info("value=[{}]", value);
CommandUser commandUser = new CommandUser("张三");
String name = commandUser.execute();
LOGGER.info("name=[{}]", name);
}
```
----
运行结果:

可以看到两个任务分成了两个线程池运行,他们之间互不干扰。
获取任务任务结果支持同步阻塞和异步非阻塞方式,可自行选择。
它的实现原理其实容易猜到:
> 利用一个 Map 来存放不同业务对应的线程池。
通过刚才的构造函数也能证明:

还要注意的一点是:
> 自定义的 Command 并不是一个单例,每次执行需要 new 一个实例,不然会报 ` This instance can only be executed once. Please instantiate a new instance.` 异常。
## 总结
池化技术确实在平时应用广泛,熟练掌握能提高不少效率。
文末的 hystrix 源码:
[https://github.com/crossoverJie/Java-Interview/tree/master/src/main/java/com/crossoverjie/hystrix](https://github.com/crossoverJie/Java-Interview/tree/master/src/main/java/com/crossoverjie/hystrix)
================================================
FILE: MD/Threadcore.md
================================================
# Java 多线程三大核心
## 原子性
`Java` 的原子性就和数据库事务的原子性差不多,一个操作中要么全部执行成功或者失败。
`JMM` 只是保证了基本的原子性,但类似于 `i++` 之类的操作,看似是原子操作,其实里面涉及到:
- 获取 i 的值。
- 自增。
- 再赋值给 i。
这三步操作,所以想要实现 `i++` 这样的原子操作就需要用到 `synchronized` 或者是 `lock` 进行加锁处理。
如果是基础类的自增操作可以使用 `AtomicInteger` 这样的原子类来实现(其本质是利用了 `CPU` 级别的 的 `CAS` 指令来完成的)。
其中用的最多的方法就是: `incrementAndGet()` 以原子的方式自增。
源码如下:
```java
public final long incrementAndGet() {
for (;;) {
long current = get();
long next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
```
首先是获得当前的值,然后自增 +1。接着则是最核心的 `compareAndSet() ` 来进行原子更新。
```java
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
```
其逻辑就是判断当前的值是否被更新过,是否等于 `current`,如果等于就说明没有更新过然后将当前的值更新为 `next`,如果不等于则返回`false` 进入循环,直到更新成功为止。
还有其中的 `get()` 方法也很关键,返回的是当前的值,当前值用了 `volatile` 关键词修饰,保证了内存可见性。
```java
private volatile int value;
```
## 可见性
现代计算机中,由于 `CPU` 直接从主内存中读取数据的效率不高,所以都会对应的 `CPU` 高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。

如上图所示。
`volatile` 关键字就是用于保证内存可见性,当线程A更新了 volatile 修饰的变量时,它会立即刷新到主线程,并且将其余缓存中该变量的值清空,导致其余线程只能去主内存读取最新值。
使用 `volatile` 关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。
`synchronized`和加锁也能能保证可见性,实现原理就是在释放锁之前其余线程是访问不到这个共享变量的。但是和 `volatile` 相比开销较大。
## 顺序性
以下这段代码:
```java
int a = 100 ; //1
int b = 200 ; //2
int c = a + b ; //3
```
正常情况下的执行顺序应该是 `1>>2>>3`。但是有时 `JVM` 为了提高整体的效率会进行指令重排导致执行的顺序可能是 `2>>1>>3`。但是 `JVM` 也不能是什么都进行重排,是在保证最终结果和代码顺序执行结果一致的情况下才可能进行重排。
重排在单线程中不会出现问题,但在多线程中会出现数据不一致的问题。
Java 中可以使用 `volatile` 来保证顺序性,`synchronized 和 lock` 也可以来保证有序性,和保证原子性的方式一样,通过同一段时间只能一个线程访问来实现的。
除了通过 `volatile` 关键字显式的保证顺序之外, `JVM` 还通过 `happen-before` 原则来隐式的保证顺序性。
其中有一条就是适用于 `volatile` 关键字的,针对于 `volatile` 关键字的写操作肯定是在读操作之前,也就是说读取的值肯定是最新的。
### volatile 的应用
#### 双重检查锁的单例模式
可以用 `volatile` 实现一个双重检查锁的单例模式:
```java
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
```
这里的 `volatile` 关键字主要是为了防止指令重排。
如果不用 `volatile` ,`singleton = new Singleton();`,这段代码其实是分为三步:
- 分配内存空间。(1)
- 初始化对象。(2)
- 将 `singleton` 对象指向分配的内存地址。(3)
加上 `volatile` 是为了让以上的三步操作顺序执行,反之有可能第三步在第二步之前被执行就有可能导致某个线程拿到的单例对象还没有初始化,以致于使用报错。
#### 控制停止线程的标记
```java
private volatile boolean flag ;
private void run(){
new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
doSomeThing();
}
}
});
}
private void stop(){
flag = false ;
}
```
这里如果没有用 volatile 来修饰 flag ,就有可能其中一个线程调用了 `stop()`方法修改了 flag 的值并不会立即刷新到主内存中,导致这个循环并不会立即停止。
这里主要利用的是 `volatile` 的内存可见性。
总结一下:
- `volatile` 关键字只能保证可见性,顺序性,**不能保证原子性**。
================================================
FILE: MD/additional-skills/how-to-use-git-efficiently.md
================================================
# 【译】如何高效的使用 Git
[原文链接](https://medium.freecodecamp.org/how-to-use-git-efficiently-54320a236369)

> 代码昨天还是运行好好的今天就不行了。
> 代码被删了。
> 突然出现了一个奇怪的 bug,但是没人知道怎么回事。
如果你出现过上面的任何一种情况,那本篇文章就是为你准备的。
除了知道 `git add`, `git commit` , `git push` 之外,Git 中还需要其他重要的技术需要掌握。长远来看对我们是有帮助的。这里我将向你展示 Git 的最佳实践。
# Git 工作流
当有多个开发者同时涉及到一个项目时那么就非常有必要正确使用 Git 工作流。
这里我将介绍一种工作流,它在一个多人大型项目中将非常有用。

# 前言
突然有一天,你成为了一个项目的技术 Leader 并计划做出下一个 Facebook。在这个项目中你有三个开发人员。
1. Alice:一个开发小白。
2. Bob:拥有一年工作经验,了解基本开发。
3. John:三年开发经验,熟练开发技能。
4. 你:该项目的技术负责人。
# Git 开发流程
## Master 分支
1. Master 分支应该始终和生产环境保持一致。
2. 由于 master 和生产代码是一致的,所以没有人包括技术负责人能在 master 上直接开发。
3. 真正的开发代码应当写在其他分支上。
## Release(发布) 分支
1. 当项目开始时,第一件事情就是创建发布分支。发布分支是基于 master 分支创建而来。
2. 所有与本项目相关的代码都在发布分支中,这个分支也是一个以 `release/` 开头的普通分支。
3. 比如这次的发布分支名为 `release/fb`。
4. 可能有多个项目都基于同一份代码运行,因此对于每一个项目来说都需要创建一个独立的发布分支。假设现在还有一个项目正在并行运行,那就得为这个项目创建一个单独的发布分支比如 `release/messenger`。
5. 需要单独的发布分支的原因是:多个并行项目是基于同一份代码运行的,但是项目之间不能有冲突。
## Feature(功能分支) branch
1. 对于应用中的每一个功能都应该创建一个独立的功能分支,这会确保这些功能能被单独构建。
2. 功能分支也和其他分支一样,只是以 `feature/` 开头。
3. 现在作为技术 Leader,你要求 Alice 去做 Facebook 的登录页面。因此他创建了一个新的功能分支。把他命名为 `feature/login`。Alice 将会在这个分支上编写所有的登录代码。
4. 这个功能分支通常是基于 Release(发布) 分支 创建而来。
5. Bob 的任务为创建添加好友页面,因此他创建了一个名为 `feature/friendrequest` 的功能分支。
6. John 则被安排构建消息流,因此创建了一个 `feature/newsfeed` 的功能分支。
7. 所有的开发人员都在自己的分支上进行开发,目前为止都很正常。
8. 现在当 Alice 完成了他的登录开发,他需要将他的功能分支 `feature/login` 发送给 Release(发布) 分支。这个过程是通过发起一个 `pull request` 完成的。
## Pull request
首先 `pull request` 不能和 `git pull` 搞混了。
开发人员不能直接向 Release(发布) 分支推送代码,技术 Leader 需要在功能分支合并到 Release(发布) 分支之前做好代码审查。这也是通过 `pull request` 完成的。
Alice 能够按照如下 GitHub 方式提交 `pull request`。

在分支名字的旁边有一个 “New pull request” 按钮,点击之后将会显示如下界面:

- 比较分支是 Alice 的功能分支 `feature/login`。
- base 分支则应该是发布分支 `release/fb`。
点击之后 Alice 需要为这个 `pull request` 输入名称和描述,最后再点击 “Create Pull Request” 按钮。
同时 Alice 需要为这个 `pull request` 指定一个 reviewer。作为技术 Leader 的你被选为本次 `pull request` 的 reviewer。
你完成代码审查之后就需要把这个功能分支合并到 Release(发布) 分支。
现在你已经把 `feature/login` 分支合并到 `release/fb`,并且 Alice 非常高兴他的代码被合并了。
## 代码冲突 😠
1. Bob 完成了他的编码工作,同时向 `release/fb` 分支发起了一个 `pull request`。
2. 因为发布分支已经合并了登录的代码,这时代码冲突发生了。解决冲突和合并代码是 reviewer 的责任。在这样的情况下,作为技术 Leader 就需要解决冲突和合并代码了。
3. 现在 John 也已经完成了他的开发,同时也想把代码合并到发布分支。但 John 非常擅长于解决代码冲突。他将 `release/fb` 上最新的代码合并到他自己的功能分支 `feature/newsfeed` (通过 git pull 或 git merge 命令)。同时他解决了所有存在的冲突,现在 `feature/newsfeed` 已经有了所有发布分支 `release/fb` 的代码。
4. 最后 John 创建了一个 `pull request`,由于 John 已经解决了所有问题,所以本次 `pull request` 不会再有冲突了。
因此通常有两种方式来解决代码冲突:
- `pull request` 的 reviewer 需要解决所有的代码冲突。
- 开发人员需要确保将发布分支的最新代码合并到功能分支,并且解决所有的冲突。
# 还是 Master 分支
一旦项目完成,发布分支的代码需要合并回 master 分支,同时需要发布到生产环境。
因此生产环境中的代码总是和 master 分支保持一致。同时对于今后的任何项目来说都是要确保 master 代码是最新的。
> 我们现在团队就是按照这样的方式进行开发,确实可以尽可能的减少代码管理上的问题。
**你的点赞与转发是最大的支持。**
================================================
FILE: MD/architecture-design/million-sms-push.md
================================================
# 设计一个百万级的消息推送系统

# 前言
首先迟到的祝大家中秋快乐。
最近一周多没有更新了。其实我一直想憋一个大招,分享一些大家感兴趣的干货。
鉴于最近我个人的工作内容,于是利用这三天小长假憋了一个出来(其实是玩了两天🤣)。
---
先简单说下本次的主题,由于我最近做的是物联网相关的开发工作,其中就不免会遇到和设备的交互。
最主要的工作就是要有一个系统来支持设备的接入、向设备推送消息;同时还得满足大量设备接入的需求。
所以本次分享的内容不但可以满足物联网领域同时还支持以下场景:
- 基于 `WEB` 的聊天系统(点对点、群聊)。
- `WEB` 应用中需求服务端推送的场景。
- 基于 SDK 的消息推送平台。
# 技术选型
要满足大量的连接数、同时支持双全工通信,并且性能也得有保障。
在 Java 技术栈中进行选型首先自然是排除掉了传统 `IO`。
那就只有选 NIO 了,在这个层面其实选择也不多,考虑到社区、资料维护等方面最终选择了 Netty。
最终的架构图如下:

现在看着蒙没关系,下文一一介绍。
# 协议解析
既然是一个消息系统,那自然得和客户端定义好双方的协议格式。
常见和简单的是 HTTP 协议,但我们的需求中有一项需要是双全工的交互方式,同时 HTTP 更多的是服务于浏览器。我们需要的是一个更加精简的协议,减少许多不必要的数据传输。
因此我觉得最好是在满足业务需求的情况下定制自己的私有协议,在我这个场景下其实有标准的物联网协议。
如果是其他场景可以借鉴现在流行的 `RPC` 框架定制私有协议,使得双方通信更加高效。
不过根据这段时间的经验来看,不管是哪种方式都得在协议中预留安全相关的位置。
协议相关的内容就不过讨论了,更多介绍具体的应用。
# 简单实现
首先考虑如何实现功能,再来思考百万连接的情况。
## 注册鉴权
在做真正的消息上、下行之前首先要考虑的就是鉴权问题。
就像你使用微信一样,第一步怎么也得是登录吧,不能无论是谁都可以直接连接到平台。
所以第一步得是注册才行。
如上面架构图中的 `注册/鉴权` 模块。通常来说都需要客户端通过 `HTTP` 请求传递一个唯一标识,后台鉴权通过之后会响应一个 `token`,并将这个 `token` 和客户端的关系维护到 `Redis` 或者是 DB 中。
客户端将这个 token 也保存到本地,今后的每一次请求都得带上这个 token。一旦这个 token 过期,客户端需要再次请求获取 token。
鉴权通过之后客户端会直接通过`TCP 长连接`到图中的 `push-server` 模块。
这个模块就是真正处理消息的上、下行。
## 保存通道关系
在连接接入之后,真正处理业务之前需要将当前的客户端和 Channel 的关系维护起来。
假设客户端的唯一标识是手机号码,那就需要把手机号码和当前的 Channel 维护到一个 Map 中。
这点和之前 [SpringBoot 整合长连接心跳机制](http://t.cn/EPcNHFZ) 类似。

同时为了可以通过 Channel 获取到客户端唯一标识(手机号码),还需要在 Channel 中设置对应的属性:
```java
public static void putClientId(Channel channel, String clientId) {
channel.attr(CLIENT_ID).set(clientId);
}
```
获取时手机号码时:
```java
public static String getClientId(Channel channel) {
return (String)getAttribute(channel, CLIENT_ID);
}
```
这样当我们客户端下线的时便可以记录相关日志:
```java
String telNo = NettyAttrUtil.getClientId(ctx.channel());
NettySocketHolder.remove(telNo);
log.info("客户端下线,TelNo=" + telNo);
```
> 这里有一点需要注意:存放客户端与 Channel 关系的 Map 最好是预设好大小(避免经常扩容),因为它将是使用最为频繁同时也是占用内存最大的一个对象。
## 消息上行
接下来则是真正的业务数据上传,通常来说第一步是需要判断上传消息输入什么业务类型。
在聊天场景中,有可能上传的是文本、图片、视频等内容。
所以我们得进行区分,来做不同的处理;这就和客户端协商的协议有关了。
- 可以利用消息头中的某个字段进行区分。
- 更简单的就是一个 `JSON` 消息,拿出一个字段用于区分不同消息。
不管是哪种只有可以区分出来即可。
### 消息解析与业务解耦
消息可以解析之后便是处理业务,比如可以是写入数据库、调用其他接口等。
我们都知道在 Netty 中处理消息一般是在 `channelRead()` 方法中。

在这里可以解析消息,区分类型。
但如果我们的业务逻辑也写在里面,那这里的内容将是巨多无比。
甚至我们分为好几个开发来处理不同的业务,这样将会出现许多冲突、难以维护等问题。
所以非常有必要将消息解析与业务处理完全分离开来。
> 这时面向接口编程就发挥作用了。
这里的核心代码和 [「造个轮子」——cicada(轻量级 WEB 框架)](https://crossoverjie.top/2018/09/03/wheel/cicada1/#%E9%85%8D%E7%BD%AE%E4%B8%9A%E5%8A%A1-Action) 是一致的。
都是先定义一个接口用于处理业务逻辑,然后在解析消息之后通过反射创建具体的对象执行其中的`处理函数`即可。
这样不同的业务、不同的开发人员只需要实现这个接口同时实现自己的业务逻辑即可。
伪代码如下:


想要了解 cicada 的具体实现请点击这里:
[https://github.com/TogetherOS/cicada](https://github.com/TogetherOS/cicada)
上行还有一点需要注意;由于是基于长连接,所以客户端需要定期发送心跳包用于维护本次连接。同时服务端也会有相应的检查,N 个时间间隔没有收到消息之后将会主动断开连接节省资源。
这点使用一个 `IdleStateHandler` 就可实现,更多内容可以查看 [Netty(一) SpringBoot 整合长连接心跳机制](https://crossoverjie.top/2018/05/24/netty/Netty(1)TCP-Heartbeat/#%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%BF%83%E8%B7%B3)。
## 消息下行
有了上行自然也有下行。比如在聊天的场景中,有两个客户端连上了 `push-server`,他们直接需要点对点通信。
这时的流程是:
- A 将消息发送给服务器。
- 服务器收到消息之后,得知消息是要发送给 B,需要在内存中找到 B 的 Channel。
- 通过 B 的 Channel 将 A 的消息转发下去。
这就是一个下行的流程。
甚至管理员需要给所有在线用户发送系统通知也是类似:
遍历保存通道关系的 Map,挨个发送消息即可。这也是之前需要存放到 Map 中的主要原因。
伪代码如下:

具体可以参考:
[https://github.com/crossoverJie/netty-action/](https://github.com/crossoverJie/netty-action/)
# 分布式方案
单机版的实现了,现在着重讲讲如何实现百万连接。
百万连接其实只是一个形容词,更多的是想表达如何来实现一个分布式的方案,可以灵活的水平拓展从而能支持更多的连接。
再做这个事前首先得搞清楚我们单机版的能支持多少连接。影响这个的因素就比较多了。
- 服务器自身配置。内存、CPU、网卡、Linux 支持的最大文件打开数等。
- 应用自身配置,因为 Netty 本身需要依赖于堆外内存,但是 JVM 本身也是需要占用一部分内存的,比如存放通道关系的大 `Map`。这点需要结合自身情况进行调整。
结合以上的情况可以测试出单个节点能支持的最大连接数。
单机无论怎么优化都是有上限的,这也是分布式主要解决的问题。
## 架构介绍
在将具体实现之前首先得讲讲上文贴出的整体架构图。

先从左边开始。
上文提到的 `注册鉴权` 模块也是集群部署的,通过前置的 Nginx 进行负载。之前也提过了它主要的目的是来做鉴权并返回一个 token 给客户端。
但是 `push-server` 集群之后它又多了一个作用。那就是得返回一台可供当前客户端使用的 `push-server`。
右侧的 `平台` 一般指管理平台,它可以查看当前的实时在线数、给指定客户端推送消息等。
推送消息则需要经过一个推送路由(`push-server`)找到真正的推送节点。
其余的中间件如:Redis、Zookeeper、Kafka、MySQL 都是为了这些功能所准备的,具体看下面的实现。
## 注册发现
首先第一个问题则是 `注册发现`,`push-server` 变为多台之后如何给客户端选择一台可用的节点是第一个需要解决的。
这块的内容其实已经在 [分布式(一) 搞定服务注册与发现](https://crossoverjie.top/2018/08/27/distributed/distributed-discovery-zk/) 中详细讲过了。
所有的 `push-server` 在启动时候需要将自身的信息注册到 Zookeeper 中。
`注册鉴权` 模块会订阅 Zookeeper 中的节点,从而可以获取最新的服务列表。结构如下:

以下是一些伪代码:
应用启动注册 Zookeeper。


对于`注册鉴权`模块来说只需要订阅这个 Zookeeper 节点:

### 路由策略
既然能获取到所有的服务列表,那如何选择一台刚好合适的 `push-server` 给客户端使用呢?
这个过程重点要考虑以下几点:
- 尽量保证各个节点的连接均匀。
- 增删节点是否要做 Rebalance。
首先保证均衡有以下几种算法:
- 轮询。挨个将各个节点分配给客户端。但会出现新增节点分配不均匀的情况。
- Hash 取模的方式。类似于 HashMap,但也会出现轮询的问题。当然也可以像 HashMap 那样做一次 Rebalance,让所有的客户端重新连接。不过这样会导致所有的连接出现中断重连,代价有点大。
- 由于 Hash 取模方式的问题带来了[`一致性 Hash`算法](https://crossoverjie.top/%2F2018%2F01%2F08%2FConsistent-Hash%2F),但依然会有一部分的客户端需要 Rebalance。
- 权重。可以手动调整各个节点的负载情况,甚至可以做成自动的,基于监控当某些节点负载较高就自动调低权重,负载较低的可以提高权重。
还有一个问题是:
> 当我们在重启部分应用进行升级时,在该节点上的客户端怎么处理?
由于我们有心跳机制,当心跳不通之后就可以认为该节点出现问题了。那就得重新请求`注册鉴权`模块获取一个可用的节点。在弱网情况下同样适用。
如果这时客户端正在发送消息,则需要将消息保存到本地等待获取到新的节点之后再次发送。
## 有状态连接
在这样的场景中不像是 HTTP 那样是无状态的,我们得明确的知道各个客户端和连接的关系。
在上文的单机版中我们将这个关系保存到本地的缓存中,但在分布式环境中显然行不通了。
比如在平台向客户端推送消息的时候,它得首先知道这个客户端的通道保存在哪台节点上。
借助我们以前的经验,这样的问题自然得引入一个第三方中间件用来存放这个关系。
也就是架构图中的存放`路由关系的 Redis`,在客户端接入 `push-server` 时需要将当前客户端唯一标识和服务节点的 `ip+port` 存进 `Redis`。
同时在客户端下线时候得在 Redis 中删掉这个连接关系。
> 这样在理想情况下各个节点内存中的 map 关系加起来应该正好等于 Redis 中的数据。
伪代码如下:

这里存放路由关系的时候会有并发问题,最好是换为一个 `lua` 脚本。
## 推送路由
设想这样一个场景:管理员需要给最近注册的客户端推送一个系统消息会怎么做?
> 结合架构图
假设这批客户端有 10W 个,首先我们需要将这批号码通过`平台`下的 `Nginx` 下发到一个推送路由中。
为了提高效率甚至可以将这批号码再次分散到每个 `push-route` 中。
拿到具体号码之后再根据号码的数量启动多线程的方式去之前的路由 Redis 中获取客户端所对应的 `push-server`。
再通过 HTTP 的方式调用 `push-server` 进行真正的消息下发(Netty 也很好的支持 HTTP 协议)。
推送成功之后需要将结果更新到数据库中,不在线的客户端可以根据业务再次推送等。
## 消息流转
也许有些场景对于客户端上行的消息非常看重,需要做持久化,并且消息量非常大。
在 `push-sever` 做业务显然不合适,这时完全可以选择 Kafka 来解耦。
将所有上行的数据直接往 Kafka 里丢后就不管了。
再由消费程序将数据取出写入数据库中即可。
其实这块内容也很值得讨论,可以先看这篇了解下:[强如 Disruptor 也发生内存溢出?](https://crossoverjie.top/2018/08/29/java-senior/OOM-Disruptor/)
后续谈到 Kafka 再做详细介绍。
# 分布式问题
分布式解决了性能问题但却带来了其他麻烦。
## 应用监控
比如如何知道线上几十个 `push-server` 节点的健康状况?
这时就得监控系统发挥作用了,我们需要知道各个节点当前的内存使用情况、GC。
以及操作系统本身的内存使用,毕竟 Netty 大量使用了堆外内存。
同时需要监控各个节点当前的在线数,以及 Redis 中的在线数。理论上这两个数应该是相等的。
这样也可以知道系统的使用情况,可以灵活的维护这些节点数量。
## 日志处理
日志记录也变得异常重要了,比如哪天反馈有个客户端一直连不上,你得知道问题出在哪里。
最好是给每次请求都加上一个 traceID 记录日志,这样就可以通过这个日志在各个节点中查看到底是卡在了哪里。
以及 ELK 这些工具都得用起来才行。
# 总结
本次是结合我日常经验得出的,有些坑可能在工作中并没有踩到,所有还会有一些遗漏的地方。
就目前来看想做一个稳定的推送系统其实是比较麻烦的,其中涉及到的点非常多,只有真正做过之后才会知道。
看完之后觉得有帮助的还请不吝转发分享。
**欢迎关注公众号一起交流:**

================================================
FILE: MD/collection/HashSet.md
================================================
# HashSet
`HashSet` 是一个不允许存储重复元素的集合,它的实现比较简单,只要理解了 `HashMap`,`HashSet` 就水到渠成了。
## 成员变量
首先了解下 `HashSet` 的成员变量:
```java
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
```
发现主要就两个变量:
- `map` :用于存放最终数据的。
- `PRESENT` :是所有写入 map 的 `value` 值。
## 构造函数
```java
public HashSet() {
map = new HashMap<>();
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
```
构造函数很简单,利用了 `HashMap` 初始化了 `map` 。
## add
```java
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
```
比较关键的就是这个 `add()` 方法。
可以看出它是将存放的对象当做了 `HashMap` 的健,`value` 都是相同的 `PRESENT` 。由于 `HashMap` 的 `key` 是不能重复的,所以每当有重复的值写入到 `HashSet` 时,`value` 会被覆盖,但 `key` 不会受到影响,这样就保证了 `HashSet` 中只能存放不重复的元素。
## 总结
`HashSet` 的原理比较简单,几乎全部借助于 `HashMap` 来实现的。
所以 `HashMap` 会出现的问题 `HashSet` 依然不能避免。
================================================
FILE: MD/collection/LinkedHashMap.md
================================================
# LinkedHashMap 底层分析
众所周知 [HashMap](https://github.com/crossoverJie/Java-Interview/blob/master/MD/HashMap.md) 是一个无序的 `Map`,因为每次根据 `key` 的 `hashcode` 映射到 `Entry` 数组上,所以遍历出来的顺序并不是写入的顺序。
因此 JDK 推出一个基于 `HashMap` 但具有顺序的 `LinkedHashMap` 来解决有排序需求的场景。
它的底层是继承于 `HashMap` 实现的,由一个双向链表所构成。
`LinkedHashMap` 的排序方式有两种:
- 根据写入顺序排序。
- 根据访问顺序排序。
其中根据访问顺序排序时,每次 `get` 都会将访问的值移动到链表末尾,这样重复操作就能得到一个按照访问顺序排序的链表。
## 数据结构
```java
@Test
public void test(){
Map<String, Integer> map = new LinkedHashMap<String, Integer>();
map.put("1",1) ;
map.put("2",2) ;
map.put("3",3) ;
map.put("4",4) ;
map.put("5",5) ;
System.out.println(map.toString());
}
```
调试可以看到 `map` 的组成:

打开源码可以看到:
```java
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
private final boolean accessOrder;
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
}
```
其中 `Entry` 继承于 `HashMap` 的 `Entry`,并新增了上下节点的指针,也就形成了双向链表。
还有一个 `header` 的成员变量,是这个双向链表的头结点。
上边的 demo 总结成一张图如下:

第一个类似于 `HashMap` 的结构,利用 `Entry` 中的 `next` 指针进行关联。
下边则是 `LinkedHashMap` 如何达到有序的关键。
就是利用了头节点和其余的各个节点之间通过 `Entry` 中的 `after` 和 `before` 指针进行关联。
其中还有一个 `accessOrder` 成员变量,默认是 `false`,默认按照插入顺序排序,为 `true` 时按照访问顺序排序,也可以调用:
```
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
```
这个构造方法可以显式的传入 `accessOrder `。
## 构造方法
`LinkedHashMap` 的构造方法:
```java
public LinkedHashMap() {
super();
accessOrder = false;
}
```
其实就是调用的 `HashMap` 的构造方法:
`HashMap` 实现:
```java
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
//HashMap 只是定义了改方法,具体实现交给了 LinkedHashMap
init();
}
```
可以看到里面有一个空的 `init()`,具体是由 `LinkedHashMap` 来实现的:
```java
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
```
其实也就是对 `header` 进行了初始化。
## put() 方法
看 `LinkedHashMap` 的 `put()` 方法之前先看看 `HashMap` 的 `put` 方法:
```java
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//空实现,交给 LinkedHashMap 自己实现
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// LinkedHashMap 对其重写
addEntry(hash, key, value, i);
return null;
}
// LinkedHashMap 对其重写
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
// LinkedHashMap 对其重写
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
```
主体的实现都是借助于 `HashMap` 来完成的,只是对其中的 `recordAccess(), addEntry(), createEntry()` 进行了重写。
`LinkedHashMap` 的实现:
```java
//就是判断是否是根据访问顺序排序,如果是则需要将当前这个 Entry 移动到链表的末尾
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
//调用了 HashMap 的实现,并判断是否需要删除最少使用的 Entry(默认不删除)
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
//就多了这一步,将新增的 Entry 加入到 header 双向链表中
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
//写入到双向链表中
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
```
## get 方法
LinkedHashMap 的 `get()` 方法也重写了:
```java
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
//多了一个判断是否是按照访问顺序排序,是则将当前的 Entry 移动到链表头部。
e.recordAccess(this);
return e.value;
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
//删除
remove();
//添加到头部
addBefore(lm.header);
}
}
```
`clear()` 清空就要比较简单了:
```java
//只需要把指针都指向自己即可,原本那些 Entry 没有引用之后就会被 JVM 自动回收。
public void clear() {
super.clear();
header.before = header.after = header;
}
```
## 总结
总的来说 `LinkedHashMap` 其实就是对 `HashMap` 进行了拓展,使用了双向链表来保证了顺序性。
因为是继承于 `HashMap` 的,所以一些 `HashMap` 存在的问题 `LinkedHashMap` 也会存在,比如不支持并发等。
================================================
FILE: MD/concurrent/thread-communication.md
================================================
# 深入理解线程通信
## 前言
开发中不免会遇到需要所有子线程执行完毕通知主线程处理某些逻辑的场景。
或者是线程 A 在执行到某个条件通知线程 B 执行某个操作。
可以通过以下几种方式实现:
## 等待通知机制
> 等待通知模式是 Java 中比较经典的线程通信方式。
两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。
如两个线程交替打印奇偶数:
```java
public class TwoThreadWaitNotify {
private int start = 1;
private boolean flag = false;
public static void main(String[] args) {
TwoThreadWaitNotify twoThread = new TwoThreadWaitNotify();
Thread t1 = new Thread(new OuNum(twoThread));
t1.setName("A");
Thread t2 = new Thread(new JiNum(twoThread));
t2.setName("B");
t1.start();
t2.start();
}
/**
* 偶数线程
*/
public static class OuNum implements Runnable {
private TwoThreadWaitNotify number;
public OuNum(TwoThreadWaitNotify number) {
this.number = number;
}
@Override
public void run() {
while (number.start <= 100) {
synchronized (TwoThreadWaitNotify.class) {
System.out.println("偶数线程抢到锁了");
if (number.flag) {
System.out.println(Thread.currentThread().getName() + "+-+偶数" + number.start);
number.start++;
number.flag = false;
TwoThreadWaitNotify.class.notify();
}else {
try {
TwoThreadWaitNotify.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 奇数线程
*/
public static class JiNum implements Runnable {
private TwoThreadWaitNotify number;
public JiNum(TwoThreadWaitNotify number) {
this.number = number;
}
@Override
public void run() {
while (number.start <= 100) {
synchronized (TwoThreadWaitNotify.class) {
System.out.println("奇数线程抢到锁了");
if (!number.flag) {
System.out.println(Thread.currentThread().getName() + "+-+奇数" + number.start);
number.start++;
number.flag = true;
TwoThreadWaitNotify.class.notify();
}else {
try {
TwoThreadWaitNotify.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
```
输出结果:
```
t2+-+奇数93
t1+-+偶数94
t2+-+奇数95
t1+-+偶数96
t2+-+奇数97
t1+-+偶数98
t2+-+奇数99
t1+-+偶数100
```
这里的线程 A 和线程 B 都对同一个对象 `TwoThreadWaitNotify.class` 获取锁,A 线程调用了同步对象的 wait() 方法释放了锁并进入 `WAITING` 状态。
B 线程调用了 notify() 方法,这样 A 线程收到通知之后就可以从 wait() 方法中返回。
这里利用了 `TwoThreadWaitNotify.class` 对象完成了通信。
有一些需要注意:
- wait() 、notify()、notifyAll() 调用的前提都是获得了对象的锁(也可称为对象监视器)。
- 调用 wait() 方法后线程会释放锁,进入 `WAITING` 状态,该线程也会被移动到**等待队列**中。
- 调用 notify() 方法会将**等待队列**中的线程移动到**同步队列**中,线程状态也会更新为 `BLOCKED`
- 从 wait() 方法返回的前提是调用 notify() 方法的线程释放锁,wait() 方法的线程获得锁。
等待通知有着一个经典范式:
线程 A 作为消费者:
1. 获取对象的锁。
2. 进入 while(判断条件),并调用 wait() 方法。
3. 当条件满足跳出循环执行具体处理逻辑。
线程 B 作为生产者:
1. 获取对象锁。
2. 更改与线程 A 共用的判断条件。
3. 调用 notify() 方法。
伪代码如下:
```
//Thread A
synchronized(Object){
while(条件){
Object.wait();
}
//do something
}
//Thread B
synchronized(Object){
条件=false;//改变条件
Object.notify();
}
```
## join() 方法
```java
private static void join() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("running");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}) ;
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("running2");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}) ;
t1.start();
t2.start();
//等待线程1终止
t1.join();
//等待线程2终止
t2.join();
LOGGER.info("main over");
}
```
输出结果:
```
2018-03-16 20:21:30.967 [Thread-1] INFO c.c.actual.ThreadCommunication - running2
2018-03-16 20:21:30.967 [Thread-0] INFO c.c.actual.ThreadCommunication - running
2018-03-16 20:21:34.972 [main] INFO c.c.actual.ThreadCommunication - main over
```
在 `t1.join()` 时会一直阻塞到 t1 执行完毕,所以最终主线程会等待 t1 和 t2 线程执行完毕。
其实从源码可以看出,join() 也是利用的等待通知机制:
核心逻辑:
```java
while (isAlive()) {
wait(0);
}
```
在 join 线程完成后会调用 notifyAll() 方法,是在 JVM 实现中调用,所以这里看不出来。
## volatile 共享内存
因为 Java 是采用共享内存的方式进行线程通信的,所以可以采用以下方式用主线程关闭 A 线程:
```java
public class Volatile implements Runnable{
private static volatile boolean flag = true ;
@Override
public void run() {
while (flag){
System.out.println(Thread.currentThread().getName() + "正在运行。。。");
}
System.out.println(Thread.currentThread().getName() +"执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Volatile aVolatile = new Volatile();
new Thread(aVolatile,"thread A").start();
System.out.println("main 线程正在运行") ;
TimeUnit.MILLISECONDS.sleep(100) ;
aVolatile.stopThread();
}
private void stopThread(){
flag = false ;
}
}
```
输出结果:
```
thread A正在运行。。。
thread A正在运行。。。
thread A正在运行。。。
thread A正在运行。。。
thread A执行完毕
```
这里的 flag 存放于主内存中,所以主线程和线程 A 都可以看到。
flag 采用 volatile 修饰主要是为了内存可见性,更多内容可以查看[这里](http://crossoverjie.top/2018/03/09/volatile/)。
## CountDownLatch 并发工具
CountDownLatch 可以实现 join 相同的功能,但是更加的灵活。
```java
private static void countDownLatch() throws Exception{
int thread = 3 ;
long start = System.currentTimeMillis();
final CountDownLatch countDown = new CountDownLatch(thread);
for (int i= 0 ;i<thread ; i++){
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("thread run");
try {
Thread.sleep(2000);
countDown.countDown();
LOGGER.info("thread end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
countDown.await();
long stop = System.currentTimeMillis();
LOGGER.info("main over total time={}",stop-start);
}
```
输出结果:
```
2018-03-16 20:19:44.126 [Thread-0] INFO c.c.actual.ThreadCommunication - thread run
2018-03-16 20:19:44.126 [Thread-2] INFO c.c.actual.ThreadCommunication - thread run
2018-03-16 20:19:44.126 [Thread-1] INFO c.c.actual.ThreadCommunication - thread run
2018-03-16 20:19:46.136 [Thread-2] INFO c.c.actual.ThreadCommunication - thread end
2018-03-16 20:19:46.136 [Thread-1] INFO c.c.actual.ThreadCommunication - thread end
2018-03-16 20:19:46.136 [Thread-0] INFO c.c.actual.ThreadCommunication - thread end
2018-03-16 20:19:46.136 [main] INFO c.c.actual.ThreadCommunication - main over total time=2012
```
CountDownLatch 也是基于 AQS(AbstractQueuedSynchronizer) 实现的,更多实现参考 [ReentrantLock 实现原理](http://crossoverjie.top/2018/01/25/ReentrantLock/)
- 初始化一个 CountDownLatch 时告诉并发的线程,然后在每个线程处理完毕之后调用 countDown() 方法。
- 该方法会将 AQS 内置的一个 state 状态 -1 。
- 最终在主线程调用 await() 方法,它会阻塞直到 `state == 0` 的时候返回。
## CyclicBarrier 并发工具
```java
private static void cyclicBarrier() throws Exception {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3) ;
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("thread run");
try {
cyclicBarrier.await() ;
} catch (Exception e) {
e.printStackTrace();
}
LOGGER.info("thread end do something");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("thread run");
try {
cyclicBarrier.await() ;
} catch (Exception e) {
e.printStackTrace();
}
LOGGER.info("thread end do something");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("thread run");
try {
Thread.sleep(5000);
cyclicBarrier.await() ;
} catch (Exception e) {
e.printStackTrace();
}
LOGGER.info("thread end do something");
}
}).start();
LOGGER.info("main thread");
}
```
CyclicBarrier 中文名叫做屏障或者是栅栏,也可以用于线程间通信。
它可以等待 N 个线程都达到某个状态后继续运行的效果。
1. 首先初始化线程参与者。
2. 调用 `await()` 将会在所有参与者线程都调用之前等待。
3. 直到所有参与者都调用了 `await()` 后,所有线程从 `await()` 返回继续后续逻辑。
运行结果:
```
2018-03-18 22:40:00.731 [Thread-0] INFO c.c.actual.ThreadCommunication - thread run
2018-03-18 22:40:00.731 [Thread-1] INFO c.c.actual.ThreadCommunication - thread run
2018-03-18 22:40:00.731 [Thread-2] INFO c.c.actual.ThreadCommunication - thread run
2018-03-18 22:40:00.731 [main] INFO c.c.actual.ThreadCommunication - main thread
2018-03-18 22:40:05.741 [Thread-0] INFO c.c.actual.ThreadCommunication - thread end do something
2018-03-18 22:40:05.741 [Thread-1] INFO c.c.actual.ThreadCommunication - thread end do something
2018-03-18 22:40:05.741 [Thread-2] INFO c.c.actual.ThreadCommunication - thread end do something
```
可以看出由于其中一个线程休眠了五秒,所有其余所有的线程都得等待这个线程调用 `await()` 。
该工具可以实现 CountDownLatch 同样的功能,但是要更加灵活。甚至可以调用 `reset()` 方法重置 CyclicBarrier (需要自行捕获 BrokenBarrierException 处理) 然后重新执行。
## 线程响应中断
```java
public class StopThread implements Runnable {
@Override
public void run() {
while ( !Thread.currentThread().isInterrupted()) {
// 线程执行具体逻辑
System.out.println(Thread.currentThread().getName() + "运行中。。");
}
System.out.println(Thread.currentThread().getName() + "退出。。");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread(), "thread A");
thread.start();
System.out.println("main 线程正在运行") ;
TimeUnit.MILLISECONDS.sleep(10) ;
thread.interrupt();
}
}
```
输出结果:
```
thread A运行中。。
thread A运行中。。
thread A退出。。
```
可以采用中断线程的方式来通信,调用了 `thread.interrupt()` 方法其实就是将 thread 中的一个标志属性置为了 true。
并不是说调用了该方法就可以中断线程,如果不对这个标志进行响应其实是没有什么作用(这里对这个标志进行了判断)。
**但是如果抛出了 InterruptedException 异常,该标志就会被 JVM 重置为 false。**
## 线程池 awaitTermination() 方法
如果是用线程池来管理线程,可以使用以下方式来让主线程等待线程池中所有任务执行完毕:
```java
private static void executorService() throws Exception{
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10) ;
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,5,1, TimeUnit.MILLISECONDS,queue) ;
poolExecutor.execute(new Runnable() {
@Override
public void run() {
LOGGER.info("running");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
poolExecutor.execute(new Runnable() {
@Override
public void run() {
LOGGER.info("running2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
poolExecutor.shutdown();
while (!poolExecutor.awaitTermination(1,TimeUnit.SECONDS)){
LOGGER.info("线程还在执行。。。");
}
LOGGER.info("main over");
}
```
输出结果:
```
2018-03-16 20:18:01.273 [pool-1-thread-2] INFO c.c.actual.ThreadCommunication - running2
2018-03-16 20:18:01.273 [pool-1-thread-1] INFO c.c.actual.ThreadCommunication - running
2018-03-16 20:18:02.273 [main] INFO c.c.actual.ThreadCommunication - 线程还在执行。。。
2018-03-16 20:18:03.278 [main] INFO c.c.actual.ThreadCommunication - 线程还在执行。。。
2018-03-16 20:18:04.278 [main] INFO c.c.actual.ThreadCommunication - main over
```
使用这个 `awaitTermination()` 方法的前提需要关闭线程池,如调用了 `shutdown()` 方法。
调用了 `shutdown()` 之后线程池会停止接受新任务,并且会平滑的关闭线程池中现有的任务。
## 管道通信
```java
public static void piped() throws IOException {
//面向于字符 PipedInputStream 面向于字节
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();
//输入输出流建立连接
writer.connect(reader);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("running");
try {
for (int i = 0; i < 10; i++) {
writer.write(i+"");
Thread.sleep(10);
}
} catch (Exception e) {
} finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("running2");
int msg = 0;
try {
while ((msg = reader.read()) != -1) {
LOGGER.info("msg={}", (char) msg);
}
} catch (Exception e) {
}
}
});
t1.start();
t2.start();
}
```
输出结果:
```
2018-03-16 19:56:43.014 [Thread-0] INFO c.c.actual.ThreadCommunication - running
2018-03-16 19:56:43.014 [Thread-1] INFO c.c.actual.ThreadCommunication - running2
2018-03-16 19:56:43.130 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=0
2018-03-16 19:56:43.132 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=1
2018-03-16 19:56:43.132 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=2
2018-03-16 19:56:43.133 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=3
2018-03-16 19:56:43.133 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=4
2018-03-16 19:56:43.133 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=5
2018-03-16 19:56:43.133 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=6
2018-03-16 19:56:43.134 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=7
2018-03-16 19:56:43.134 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=8
2018-03-16 19:56:43.134 [Thread-1] INFO c.c.actual.ThreadCommunication - msg=9
```
Java 虽说是基于内存通信的,但也可以使用管道通信。
需要注意的是,输入流和输出流需要首先建立连接。这样线程 B 就可以收到线程 A 发出的消息了。
实际开发中可以灵活根据需求选择最适合的线程通信方式。
================================================
FILE: MD/concurrent/volatile.md
================================================
# 你应该知道的 volatile 关键字
## 前言
不管是在面试还是实际开发中 `volatile` 都是一个应该掌握的技能。
首先来看看为什么会出现这个关键字。
## 内存可见性
由于 `Java` 内存模型(`JMM`)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。
> 这里所提到的主内存可以简单认为是**堆内存**,而工作内存则可以认为是**栈内存**。
如下图所示:

所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。
显然这肯定是会出问题的,因此 `volatile` 的作用出现了:
> 当一个变量被 `volatile` 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
*`volatile` 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中*。
### 内存可见性的应用
当我们需要在两个线程间依据主内存通信时,通信的那个变量就必须的用 `volatile` 来修饰:
```java
public class Volatile implements Runnable{
private static volatile boolean flag = true ;
@Override
public void run() {
while (flag){
}
System.out.println(Thread.currentThread().getName() +"执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Volatile aVolatile = new Volatile();
new Thread(aVolatile,"thread A").start();
System.out.println("main 线程正在运行") ;
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
String value = sc.next();
if(value.equals("1")){
new Thread(new Runnable() {
@Override
public void run() {
aVolatile.stopThread();
}
}).start();
break ;
}
}
System.out.println("主线程退出了!");
}
private void stopThread(){
flag = false ;
}
}
```
主线程在修改了标志位使得线程 A 立即停止,如果没有用 `volatile` 修饰,就有可能出现延迟。
但这里有个误区,这样的使用方式容易给人的感觉是:
> 对 `volatile` 修饰的变量进行并发操作是线程安全的。
这里要重点强调,`volatile` 并**不能**保证线程安全性!
如下程序:
```java
public class VolatileInc implements Runnable{
private static volatile int count = 0 ; //使用 volatile 修饰基本数据内存不能保证原子性
//private static AtomicInteger count = new AtomicInteger() ;
@Override
public void run() {
for (int i=0;i<10000 ;i++){
count ++ ;
//count.incrementAndGet() ;
}
}
public static void main(String[] args) throws InterruptedException {
VolatileInc volatileInc = new VolatileInc() ;
Thread t1 = new Thread(volatileInc,"t1") ;
Thread t2 = new Thread(volatileInc,"t2") ;
t1.start();
//t1.join();
t2.start();
//t2.join();
for (int i=0;i<10000 ;i++){
count ++ ;
//count.incrementAndGet();
}
System.out.println("最终Count="+count);
}
}
```
当我们三个线程(t1,t2,main)同时对一个 `int` 进行累加时会发现最终的值都会小于 30000。
> 这是因为虽然 `volatile` 保证了内存可见性,每个线程拿到的值都是最新值,但 `count ++` 这个操作并不是原子的,这里面涉及到获取值、自增、赋值的操作并不能同时完成。
>
- 所以想到达到线程安全可以使这三个线程串行执行(其实就是单线程,没有发挥多线程的优势)。
- 也可以使用 `synchronized` 或者是锁的方式来保证原子性。
- 还可以用 `Atomic` 包中 `AtomicInteger` 来替换 `int`,它利用了 `CAS` 算法来保证了原子性。
## 指令重排
内存可见性只是 `volatile` 的其中一个语义,它还可以防止 `JVM` 进行指令重排优化。
举一个伪代码:
```java
int a=10 ;//1
int b=20 ;//2
int c= a+b ;//3
```
一段特别简单的代码,理想情况下它的执行顺序是:`1>2>3`。但有可能经过 JVM 优化之后的执行顺序变为了 `2>1>3`。
可以发现不管 JVM 怎么优化,前提都是保证单线程中最终结果不变的情况下进行的。
可能这里还看不出有什么问题,那看下一段伪代码:
```java
private static Map<String,String> value ;
private static volatile boolean flag = fasle ;
//以下方法发生在线程 A 中 初始化 Map
public void initMap(){
//耗时操作
value = getMapValue() ;//1
flag = true ;//2
}
//发生在线程 B中 等到 Map 初始化成功进行其他操作
public void doSomeThing(){
while(!flag){
sleep() ;
}
//dosomething
doSomeThing(value);
}
```
这里就能看出问题了,当 `flag` 没有被 `volatile` 修饰时,`JVM` 对 1 和 2 进行重排,导致 `value` 都还没有被初始化就有可能被线程 B 使用了。
所以加上 `volatile` 之后可以防止这样的重排优化,保证业务的正确性。
### 指令重排的的应用
一个经典的使用场景就是双重懒加载的单例模式了:
```java
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
//防止指令重排
singleton = new Singleton();
}
}
}
return singleton;
}
}
```
这里的 `volatile` 关键字主要是为了防止指令重排。
如果不用 ,`singleton = new Singleton();`,这段代码其实是分为三步:
- 分配内存空间。(1)
- 初始化对象。(2)
- 将 `singleton` 对象指向分配的内存地址。(3)
加上 `volatile` 是为了让以上的三步操作顺序执行,反之有可能第二步在第三步之前被执行就有可能某个线程拿到的单例对象是还没有初始化的,以致于报错。
## 总结
`volatile` 在 `Java` 并发中用的很多,比如像 `Atomic` 包中的 `value`、以及 `AbstractQueuedLongSynchronizer` 中的 `state` 都是被定义为 `volatile` 来用于保证内存可见性。
将这块理解透彻对我们编写并发程序时可以提供很大帮助。
================================================
FILE: MD/distributed/Distributed-Limit.md
================================================

## 前言
本文接着上文[应用限流](http://crossoverjie.top/2017/08/11/sbc4/)进行讨论。
之前谈到的限流方案只能针对于单个 JVM 有效,也就是单机应用。而对于现在普遍的分布式应用也得有一个分布式限流的方案。
基于此尝试写了这个组件:
[https://github.com/crossoverJie/distributed-redis-tool](https://github.com/crossoverJie/distributed-redis-tool)
## DEMO
以下采用的是
[https://github.com/crossoverJie/springboot-cloud](https://github.com/crossoverJie/springboot-cloud)
来做演示。
在 Order 应用提供的接口中采取了限流。首先是配置了限流工具的 Bean:
```java
@Configuration
public class RedisLimitConfig {
@Value("${redis.limit}")
private int limit;
@Autowired
private JedisConnectionFactory jedisConnectionFactory;
@Bean
public RedisLimit build() {
RedisClusterConnection clusterConnection = jedisConnectionFactory.getClusterConnection();
JedisCluster jedisCluster = (JedisCluster) clusterConnection.getNativeConnection();
RedisLimit redisLimit = new RedisLimit.Builder<>(jedisCluster)
.limit(limit)
.build();
return redisLimit;
}
}
```
接着在 Controller 使用组件:
```java
@Autowired
private RedisLimit redisLimit ;
@Override
@CheckReqNo
public BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) {
BaseResponse<OrderNoResVO> res = new BaseResponse();
//限流
boolean limit = redisLimit.limit();
if (!limit){
res.setCode(StatusEnum.REQUEST_LIMIT.getCode());
res.setMessage(StatusEnum.REQUEST_LIMIT.getMessage());
return res ;
}
res.setReqNo(orderNoReq.getReqNo());
if (null == orderNoReq.getAppId()){
throw new SBCException(StatusEnum.FAIL);
}
OrderNoResVO orderNoRes = new OrderNoResVO() ;
orderNoRes.setOrderId(DateUtil.getLongTime());
res.setCode(StatusEnum.SUCCESS.getCode());
res.setMessage(StatusEnum.SUCCESS.getMessage());
res.setDataBody(orderNoRes);
return res ;
}
```
为了方便使用,也提供了注解:
```java
@Override
@ControllerLimit
public BaseResponse<OrderNoResVO> getOrderNoLimit(@RequestBody OrderNoReqVO orderNoReq) {
BaseResponse<OrderNoResVO> res = new BaseResponse();
// 业务逻辑
return res ;
}
```
该注解拦截了 http 请求,会再请求达到阈值时直接返回。
普通方法也可使用:
```java
@CommonLimit
public void doSomething(){}
```
会在调用达到阈值时抛出异常。
为了模拟并发,在 [User](https://github.com/crossoverJie/springboot-cloud/blob/master/sbc-user/user/src/main/java/com/crossoverJie/sbcuser/controller/UserController.java#L72-L91) 应用中开启了 10 个线程调用 Order(**限流次数为5**) 接口(也可使用专业的并发测试工具 JMeter 等)。
```java
@Override
public BaseResponse<UserResVO> getUserByFeign(@RequestBody UserReqVO userReq) {
//调用远程服务
OrderNoReqVO vo = new OrderNoReqVO();
vo.setAppId(1L);
vo.setReqNo(userReq.getReqNo());
for (int i = 0; i < 10; i++) {
executorService.execute(new Worker(vo, orderServiceClient));
}
UserRes userRes = new UserRes();
userRes.setUserId(123);
userRes.setUserName("张三");
userRes.setReqNo(userReq.getReqNo());
userRes.setCode(StatusEnum.SUCCESS.getCode());
userRes.setMessage("成功");
return userRes;
}
private static class Worker implements Runnable {
private OrderNoReqVO vo;
private OrderServiceClient orderServiceClient;
public Worker(OrderNoReqVO vo, OrderServiceClient orderServiceClient) {
this.vo = vo;
this.orderServiceClient = orderServiceClient;
}
@Override
public void run() {
BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNoCommonLimit(vo);
logger.info("远程返回:" + JSON.toJSONString(orderNo));
}
}
```
> 为了验证分布式效果启动了两个 Order 应用。

效果如下:



## 实现原理
实现原理其实很简单。既然要达到分布式全局限流的效果,那自然需要一个第三方组件来记录请求的次数。
其中 Redis 就非常适合这样的场景。
- 每次请求时将当前时间(精确到秒)作为 Key 写入到 Redis 中,超时时间设置为 2 秒,Redis 将该 Key 的值进行自增。
- 当达到阈值时返回错误。
- 写入 Redis 的操作用 Lua 脚本来完成,利用 Redis 的单线程机制可以保证每个 Redis 请求的原子性。
Lua 脚本如下:
```lua
--lua 下标从 1 开始
-- 限流 key
local key = KEYS[1]
-- 限流大小
local limit = tonumber(ARGV[1])
-- 获取当前流量大小
local curentLimit = tonumber(redis.call('get', key) or "0")
if curentLimit + 1 > limit then
-- 达到限流大小 返回
return 0;
else
-- 没有达到阈值 value + 1
redis.call("INCRBY", key, 1)
redis.call("EXPIRE", key, 2)
return curentLimit + 1
end
```
Java 中的调用逻辑:
```java
public boolean limit() {
String key = String.valueOf(System.currentTimeMillis() / 1000);
Object result = null;
if (jedis instanceof Jedis) {
result = ((Jedis) this.jedis).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));
} else if (jedis instanceof JedisCluster) {
result = ((JedisCluster) this.jedis).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));
} else {
//throw new RuntimeException("instance is error") ;
return false;
}
if (FAIL_CODE != (Long) result) {
return true;
} else {
return false;
}
}
```
所以只需要在需要限流的地方调用该方法对返回值进行判断即可达到限流的目的。
当然这只是利用 Redis 做了一个粗暴的计数器,如果想实现类似于上文中的令牌桶算法可以基于 Lua 自行实现。
### Builder 构建器
在设计这个组件时想尽量的提供给使用者清晰、可读性、不易出错的 API。
> 比如第一步,如何构建一个限流对象。
最常用的方式自然就是构造函数,如果有多个域则可以采用重叠构造器的方式:
```java
public A(){}
public A(int a){}
public A(int a,int b){}
```
缺点也是显而易见的:如果参数过多会导致难以阅读,甚至如果参数类型一致的情况下客户端颠倒了顺序,但不会引起警告从而出现难以预测的结果。
第二种方案可以采用 JavaBean 模式,利用 `setter` 方法进行构建:
```java
A a = new A();
a.setA(a);
a.setB(b);
```
这种方式清晰易读,但却容易让对象处于不一致的状态,使对象处于线程不安全的状态。
所以这里采用了第三种创建对象的方式,构建器:
```java
public class RedisLimit {
private JedisCommands jedis;
private int limit = 200;
private static final int FAIL_CODE = 0;
/**
* lua script
*/
private String script;
private RedisLimit(Builder builder) {
this.limit = builder.limit ;
this.jedis = builder.jedis ;
buildScript();
}
/**
* limit traffic
* @return if true
*/
public boolean limit() {
String key = String.valueOf(System.currentTimeMillis() / 1000);
Object result = null;
if (jedis instanceof Jedis) {
result = ((Jedis) this.jedis).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));
} else if (jedis instanceof JedisCluster) {
result = ((JedisCluster) this.jedis).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));
} else {
//throw new RuntimeException("instance is error") ;
return false;
}
if (FAIL_CODE != (Long) result) {
return true;
} else {
return false;
}
}
/**
* read lua script
*/
private void buildScript() {
script = ScriptUtil.getScript("limit.lua");
}
/**
* the builder
* @param <T>
*/
public static class Builder<T extends JedisCommands>{
private T jedis = null ;
private int limit = 200;
public Builder(T jedis){
this.jedis = jedis ;
}
public Builder limit(int limit){
this.limit = limit ;
return this;
}
public RedisLimit build(){
return new RedisLimit(this) ;
}
}
}
```
这样客户端在使用时:
```java
RedisLimit redisLimit = new RedisLimit.Builder<>(jedisCluster)
.limit(limit)
.build();
```
更加的简单直接,并且避免了将创建过程分成了多个子步骤。
这在有多个构造参数,但又不是必选字段时很有作用。
因此顺便将分布式锁的构建器方式也一并更新了:
[https://github.com/crossoverJie/distributed-redis-tool#features](https://github.com/crossoverJie/distributed-redis-tool#features)
> 更多内容可以参考 Effective Java
### API
从上文可以看出,使用过程就是调用 `limit` 方法。
```java
//限流
boolean limit = redisLimit.limit();
if (!limit){
//具体限流逻辑
}
```
为了减少侵入性,也为了简化客户端提供了两种注解方式。
#### @ControllerLimit
该注解可以作用于 `@RequestMapping` 修饰的接口中,并会在限流后提供限流响应。
实现如下:
```java
@Component
public class WebIntercept extends WebMvcConfigurerAdapter {
private static Logger logger = LoggerFactory.getLogger(WebIntercept.class);
@Autowired
private RedisLimit redisLimit;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor())
.addPathPatterns("/**");
}
private class CustomInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (redisLimit == null) {
throw new NullPointerException("redisLimit is null");
}
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
ControllerLimit annotation = method.getMethodAnnotation(ControllerLimit.class);
if (annotation == null) {
//skip
return true;
}
boolean limit = redisLimit.limit();
if (!limit) {
logger.warn("request has bean limit");
response.sendError(500, "request limit");
return false;
}
}
return true;
}
}
}
```
其实就是实现了 SpringMVC 中的拦截器,并在拦截过程中判断是否有使用注解,从而调用限流逻辑。
**前提是应用需要扫描到该类,让 Spring 进行管理。**
```java
@ComponentScan(value = "com.crossoverjie.distributed.intercept")
```
#### @CommonLimit
当然也可以在普通方法中使用。实现原理则是 Spring AOP (SpringMVC 的拦截器本质也是 AOP)。
```java
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class CommonAspect {
private static Logger logger = LoggerFactory.getLogger(CommonAspect.class);
@Autowired
private RedisLimit redisLimit ;
@Pointcut("@annotation(com.crossoverjie.distributed.annotation.CommonLimit)")
private void check(){}
@Before("check()")
public void before(JoinPoint joinPoint) throws Exception {
if (redisLimit == null) {
throw new NullPointerException("redisLimit is null");
}
boolean limit = redisLimit.limit();
if (!limit) {
logger.warn("request has bean limit");
throw new RuntimeException("request has bean limit") ;
}
}
}
```
很简单,也是在拦截过程中调用限流。
当然使用时也得扫描到该包:
```java
@ComponentScan(value = "com.crossoverjie.distributed.intercept")
```
### 总结
**限流**在一个高并发大流量的系统中是保护应用的一个利器,成熟的方案也很多,希望对刚了解这一块的朋友提供一些思路。
以上所有的源码:
- [https://github.com/crossoverJie/distributed-redis-tool](https://github.com/crossoverJie/distributed-redis-tool)
- [https://github.com/crossoverJie/springboot-cloud](https://github.com/crossoverJie/springboot-cloud)
感兴趣的朋友可以点个 Star 或是提交 PR。
================================================
FILE: MD/distributed/distributed-lock-redis.md
================================================

## 前言
分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三。
首先谈到分布式锁自然也就联想到分布式应用。
在我们将应用拆分为分布式应用之前的单机系统中,对一些并发场景读取公共资源时如扣库存,卖车票之类的需求可以简单的使用[同步](http://crossoverjie.top/2018/01/14/Synchronize/)或者是[加锁](http://crossoverjie.top/2018/01/25/ReentrantLock/)就可以实现。
但是应用分布式了之后系统由以前的单进程多线程的程序变为了多进程多线程,这时使用以上的解决方案明显就不够了。
因此业界常用的解决方案通常是借助于一个第三方组件并利用它自身的排他性来达到多进程的互斥。如:
- 基于 DB 的唯一索引。
- 基于 ZK 的临时有序节点。
- 基于 Redis 的 `NX EX` 参数。
这里主要基于 Redis 进行讨论。
<!--more-->
## 实现
既然是选用了 Redis,那么它就得具有排他性才行。同时它最好也有锁的一些基本特性:
- 高性能(加、解锁时高性能)
- 可以使用阻塞锁与非阻塞锁。
- 不能出现死锁。
- 可用性(不能出现节点 down 掉后加锁失败)。
这里利用 `Redis set key` 时的一个 NX 参数可以保证在这个 key 不存在的情况下写入成功。并且再加上 EX 参数可以让该 key 在超时之后自动删除。
所以利用以上两个特性可以保证在同一时刻只会有一个进程获得锁,并且不会出现死锁(最坏的情况就是超时自动删除 key)。
### 加锁
实现代码如下:
```java
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
public boolean tryLock(String key, String request) {
String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);
if (LOCK_MSG.equals(result)){
return true ;
}else {
return false ;
}
}
```
注意这里使用的 jedis 的
```java
String set(String key, String value, String nxxx, String expx, long time);
```
api。
该命令可以保证 NX EX 的原子性。
一定不要把两个命令(NX EX)分开执行,如果在 NX 之后程序出现问题就有可能产生死锁。
#### 阻塞锁
同时也可以实现一个阻塞锁:
```java
//一直阻塞
public void lock(String key, String request) throws InterruptedException {
for (;;){
String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);
if (LOCK_MSG.equals(result)){
break ;
}
//防止一直消耗 CPU
Thread.sleep(DEFAULT_SLEEP_TIME) ;
}
}
//自定义阻塞时间
public boolean lock(String key, String request,int blockTime) throws InterruptedException {
while (blockTime >= 0){
String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);
if (LOCK_MSG.equals(result)){
return true ;
}
blockTime -= DEFAULT_SLEEP_TIME ;
Thread.sleep(DEFAULT_SLEEP_TIME) ;
}
return false ;
}
```
### 解锁
解锁也很简单,其实就是把这个 key 删掉就万事大吉了,比如使用 `del key` 命令。
但现实往往没有那么 easy。
如果进程 A 获取了锁设置了超时时间,但是由于执行周期较长导致到了超时时间之后锁就自动释放了。这时进程 B 获取了该锁执行很快就释放锁。这样就会出现进程 B 将进程 A 的锁释放了。
所以最好的方式是在每次解锁时都需要判断锁**是否是自己**的。
这时就需要结合加锁机制一起实现了。
加锁时需要传递一个参数,将该参数作为这个 key 的 value,这样每次解锁时判断 value 是否相等即可。
所以解锁代码就不能是简单的 `del`了。
```java
public boolean unlock(String key,String request){
//lua script
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = null ;
if (jedis instanceof Jedis){
result = ((Jedis)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));
}else if (jedis instanceof JedisCluster){
result = ((JedisCluster)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));
}else {
//throw new RuntimeException("instance is error") ;
return false ;
}
if (UNLOCK_MSG.equals(result)){
return true ;
}else {
return false ;
}
}
```
这里使用了一个 `lua` 脚本来判断 value 是否相等,相等才执行 del 命令。
使用 `lua` 也可以保证这里两个操作的原子性。
因此上文提到的四个基本特性也能满足了:
- 使用 Redis 可以保证性能。
- 阻塞锁与非阻塞锁见上文。
- 利用超时机制解决了死锁。
- Redis 支持集群部署提高了可用性。
## 使用
我自己有撸了一个完整的实现,并且已经用于了生产,有兴趣的朋友可以开箱使用:
maven 依赖:
```xml
<dependency>
<groupId>top.crossoverjie.opensource</groupId>
<artifactId>distributed-redis-lock</artifactId>
<version>1.0.0</version>
</dependency>
```
配置 bean :
```java
@Configuration
public class RedisLockConfig {
@Bean
public RedisLock build(){
RedisLock redisLock = new RedisLock() ;
HostAndPort hostAndPort = new HostAndPort("127.0.0.1",7000) ;
JedisCluster jedisCluster = new JedisCluster(hostAndPort) ;
// Jedis 或 JedisCluster 都可以
redisLock.setJedisCluster(jedisCluster) ;
return redisLock ;
}
}
```
使用:
```java
@Autowired
private RedisLock redisLock ;
public void use() {
String key = "key";
String request = UUID.randomUUID().toString();
try {
boolean locktest = redisLock.tryLock(key, request);
if (!locktest) {
System.out.println("locked error");
return;
}
//do something
} finally {
redisLock.unlock(key,request) ;
}
}
```
使用很简单。这里主要是想利用 Spring 来帮我们管理 RedisLock 这个单例的 bean,所以在释放锁的时候需要手动(因为整个上下文只有一个 RedisLock 实例)的传入 key 以及 request(api 看起来不是特别优雅)。
也可以在每次使用锁的时候 new 一个 RedisLock 传入 key 以及 request,这样倒是在解锁时很方便。但是需要自行管理 RedisLock 的实例。各有优劣吧。
项目源码在:
[https://github.com/crossoverJie/distributed-redis-tool](https://github.com/crossoverJie/distributed-redis-tool)
欢迎讨论。
## 单测
在做这个项目的时候让我不得不想提一下**单测**。
因为这个应用是强依赖于第三方组件的(Redis),但是在单测中我们需要排除掉这种依赖。比如其他伙伴 fork 了该项目想在本地跑一遍单测,结果运行不起来:
1. 有可能是 Redis 的 ip、端口和单测里的不一致。
2. Redis 自身可能也有问题。
3. 也有可能是该同学的环境中并没有 Redis。
所以最好是要把这些外部不稳定的因素排除掉,单测只测我们写好的代码。
于是就可以引入单测利器 `Mock` 了。
它的想法很简答,就是要把你所依赖的外部资源统统屏蔽掉。如:数据库、外部接口、外部文件等等。
使用方式也挺简单,可以参考该项目的单测:
```java
@Test
public void tryLock() throws Exception {
String key = "test";
String request = UUID.randomUUID().toString();
Mockito.when(jedisCluster.set(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),
Mockito.anyString(), Mockito.anyLong())).thenReturn("OK");
boolean locktest = redisLock.tryLock(key, request);
System.out.println("locktest=" + locktest);
Assert.assertTrue(locktest);
//check
Mockito.verify(jedisCluster).set(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),
Mockito.anyString(), Mockito.anyLong());
}
```
这里只是简单演示下,可以的话下次仔细分析分析。
它的原理其实也挺简单,debug 的话可以很直接的看出来:

这里我们所依赖的 JedisCluster 其实是一个 `cglib 代理对象`。所以也不难想到它是如何工作的。
比如这里我们需要用到 JedisCluster 的 set 函数并需要它的返回值。
Mock 就将该对象代理了,并在实际执行 set 方法后给你返回了一个你自定义的值。
这样我们就可以随心所欲的测试了,**完全把外部依赖所屏蔽了**。
## 总结
至此一个基于 Redis 的分布式锁完成,但是依然有些问题。
- 如在 key 超时之后业务并没有执行完毕但却自动释放锁了,这样就会导致并发问题。
- 就算 Redis 是集群部署的,如果每个节点都只是 master 没有 slave,那么 master 宕机时该节点上的所有 key 在那一时刻都相当于是释放锁了,这样也会出现并发问题。就算是有 slave 节点,但如果在数据同步到 salve 之前 master 宕机也是会出现上面的问题。
感兴趣的朋友还可以参考 [Redisson](https://github.com/redisson/redisson) 的实现。
================================================
FILE: MD/jvm/OOM-Disruptor.md
================================================

# 前言
`OutOfMemoryError` 问题相信很多朋友都遇到过,相对于常见的业务异常(数组越界、空指针等)来说这类问题是很难定位和解决的。
本文以最近碰到的一次线上内存溢出的定位、解决问题的方式展开;希望能对碰到类似问题的同学带来思路和帮助。
主要从`表现-->排查-->定位-->解决` 四个步骤来分析和解决问题。
# 表象
最近我们生产上的一个应用不断的爆出内存溢出,并且随着业务量的增长出现的频次越来越高。
该程序的业务逻辑非常简单,就是从 Kafka 中将数据消费下来然后批量的做持久化操作。
而现象则是随着 Kafka 的消息越多,出现的异常的频次就越快。由于当时还有其他工作所以只能让运维做重启,并且监控好堆内存以及 GC 情况。
> 重启大法虽好,可是依然不能根本解决问题。
# 排查
于是我们想根据运维之前收集到的内存数据、GC 日志尝试判断哪里出现问题。

结果发现老年代的内存使用就算是发生 GC 也一直居高不下,而且随着时间推移也越来越高。
结合 jstat 的日志发现就算是发生了 FGC 老年代也已经回收不了,内存已经到顶。

甚至有几台应用 FGC 达到了上百次,时间也高的可怕。
这说明应用的内存使用肯定是有问题的,有许多赖皮对象始终回收不掉。
# 定位
由于生产上的内存 dump 文件非常大,达到了几十G。也是由于我们的内存设置太大有关。
所以导致想使用 MAT 分析需要花费大量时间。
因此我们便想是否可以在本地复现,这样就要好定位的多。
为了尽快的复现问题,我将本地应用最大堆内存设置为 150M。
然后在消费 Kafka 那里 Mock 为一个 while 循环一直不断的生成数据。
同时当应用启动之后利用 VisualVM 连上应用实时监控内存、GC 的使用情况。
结果跑了 10 几分钟内存使用并没有什么问题。根据图中可以看出,每产生一次 GC 内存都能有效的回收,所以这样并没有复现问题。

没法复现问题就很难定位了。于是我们 review 代码,发现生产的逻辑和我们用 while 循环 Mock 数据还不太一样。
查看生产的日志发现每次从 Kafka 中取出的都是几百条数据,而我们 Mock 时每次只能产生**一条**。
为了尽可能的模拟生产情况便在服务器上跑着一个生产者程序,一直源源不断的向 Kafka 中发送数据。
果然不出意外只跑了一分多钟内存就顶不住了,观察左图发现 GC 的频次非常高,但是内存的回收却是相形见拙。

同时后台也开始打印内存溢出了,这样便复现出问题。
# 解决
从目前的表现来看就是内存中有许多对象一直存在强引用关系导致得不到回收。
于是便想看看到底是什么对象占用了这么多的内存,利用 VisualVM 的 HeapDump 功能可以立即 dump 出当前应用的内存情况。

结果发现 `com.lmax.disruptor.RingBuffer` 类型的对象占用了将近 50% 的内存。
看到这个包自然就想到了 `Disruptor` 环形队列。
再次 review 代码发现:从 Kafka 里取出的 700 条数据是直接往 Disruptor 里丢的。
这里也就能说明为什么第一次模拟数据没复现问题了。
模拟的时候是一个对象放进队列里,而生产的情况是 700 条数据放进队列里。这个数据量是 700 倍的差距。
而 Disruptor 作为一个环形队列,再对象没有被覆盖之前是一直存在的。
我也做了一个实验,证明确实如此。

我设置队列大小为 8 ,从 0~9 往里面写 10 条数据,当写到 8 的时候就会把之前 0 的位置覆盖掉,后面的以此类推(类似于 HashMap 的取模定位)。
所以在生产上假设我们的队列大小是 1024,那么随着系统的运行最终肯定会导致 1024 个位置上装满了对象,而且每个位置是 700 个!
于是查看了生产上 Disruptor 的 RingBuffer 配置,结果是:`1024*1024`。
这个数量级就非常吓人了。
为了验证是否是这个问题,我在本地将该值换为 2 ,一个最小值试试。
同样的 128M 内存,也是通过 Kafka 一直源源不断的取出数据。通过监控如下:

跑了 20 几分钟系统一切正常,每当一次 GC 都能回收大部分内存,最终呈现锯齿状。
这样问题就找到了,不过生产上这个值具体设置多少还得根据业务情况测试才能知道,但原有的 1024*1024 是绝对不能再使用了。
# 总结
虽然到了最后也就改了一行代码(还没改,直接修改配置),但这排查过程我觉得是有意义的。
也会让大部分觉得 JVM 这样的黑盒难以下手的同学有一个直观的感受。
`同时也得感叹 Disruptor 东西虽好,也不能乱用哦!`
相关演示代码查看:
[https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor](https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor)
**你的点赞与转发是最大的支持。**
================================================
FILE: MD/kafka/kafka-product.md
================================================
# 从源码分析如何优雅的使用 Kafka 生产者

# 前言
在上文 [设计一个百万级的消息推送系统](https://crossoverjie.top/2018/09/25/netty/million-sms-push/) 中提到消息流转采用的是 `Kafka` 作为中间件。
其中有朋友咨询在大量消息的情况下 `Kakfa` 是如何保证消息的高效及一致性呢?
正好以这个问题结合 `Kakfa` 的源码讨论下如何正确、高效的发送消息。
> 内容较多,对源码感兴趣的朋友请系好安全带😏(源码基于 `v0.10.0.0` 版本分析)。同时最好是有一定的 Kafka 使用经验,知晓基本的用法。
# 简单的消息发送
在分析之前先看一个简单的消息发送是怎么样的。
> 以下代码基于 SpringBoot 构建。
首先创建一个 `org.apache.kafka.clients.producer.Producer` 的 bean。

主要关注 `bootstrap.servers`,它是必填参数。指的是 Kafka 集群中的 broker 地址,例如 `127.0.0.1:9094`。
> 其余几个参数暂时不做讨论,后文会有详细介绍。
接着注入这个 bean 即可调用它的发送函数发送消息。

这里我给某一个 Topic 发送了 10W 条数据,运行程序消息正常发送。
但这仅仅只是做到了消息发送,对消息是否成功送达完全没管,等于是纯`异步`的方式。
## 同步
那么我想知道消息到底发送成功没有该怎么办呢?
其实 `Producer` 的 `API` 已经帮我们考虑到了,发送之后只需要调用它的 `get()` 方法即可同步获取发送结果。

发送结果:

这样的发送效率其实是比较低下的,因为每次都需要同步等待消息发送的结果。
## 异步
为此我们应当采取异步的方式发送,其实 `send()` 方法默认则是异步的,只要不手动调用 `get()` 方法。
但这样就没法获知发送结果。
所以查看 `send()` 的 API 可以发现还有一个参数。
```java
Future<RecordMetadata> send(ProducerRecord<K, V> producer, Callback callback);
```
`Callback` 是一个回调接口,在消息发送完成之后可以回调我们自定义的实现。

执行之后的结果:

同样的也能获取结果,同时发现回调的线程并不是上文同步时的`主线程`,这样也能证明是异步回调的。
同时回调的时候会传递两个参数:
- `RecordMetadata` 和上文一致的消息发送成功后的元数据。
- `Exception` 消息发送过程中的异常信息。
但是这两个参数并不会同时都有数据,只有发送失败才会有异常信息,同时发送元数据为空。
所以正确的写法应当是:

> 至于为什么会只有参数一个有值,在下文的源码分析中会一一解释。
# 源码分析
现在只掌握了基本的消息发送,想要深刻的理解发送中的一些参数配置还是得源码说了算。
首先还是来谈谈消息发送时的整个流程是怎么样的,`Kafka` 并不是简单的把消息通过网络发送到了 `broker` 中,在 Java 内部还是经过了许多优化和设计。
## 发送流程
为了直观的了解发送的流程,简单的画了几个在发送过程中关键的步骤。

从上至下依次是:
- 初始化以及真正发送消息的 `kafka-producer-network-thread` IO 线程。
- 将消息序列化。
- 得到需要发送的分区。
- 写入内部的一个缓存区中。
- 初始化的 IO 线程不断的消费这个缓存来发送消息。
## 步骤解析
接下来详解每个步骤。
### 初始化

调用该构造方法进行初始化时,不止是简单的将基本参数写入 `KafkaProducer`。比较麻烦的是初始化 `Sender` 线程进行缓冲区消费。
初始化 IO 线程处:

可以看到 Sender 线程有需要成员变量,比如:
```
acks,retries,requestTimeout
```
等,这些参数会在后文分析。
### 序列化消息
在调用 `send()` 函数后其实第一步就是序列化,毕竟我们的消息需要通过网络才能发送到 Kafka。

其中的 `valueSerializer.serialize(record.topic(), record.value());` 是一个接口,我们需要在初始化时候指定序列化实现类。

我们也可以自己实现序列化,只需要实现 `org.apache.kafka.common.serialization.Serializer` 接口即可。
### 路由分区
接下来就是路由分区,通常我们使用的 `Topic` 为了实现扩展性以及高性能都会创建多个分区。
如果是一个分区好说,所有消息都往里面写入即可。
但多个分区就不可避免需要知道写入哪个分区。
通常有三种方式。
#### 指定分区
可以在构建 `ProducerRecord` 为每条消息指定分区。

这样在路由时会判断是否有指定,有就直接使用该分区。

这种一般在特殊场景下会使用。
#### 自定义路由策略

如果没有指定分区,则会调用 `partitioner.partition` 接口执行自定义分区策略。
而我们也只需要自定义一个类实现 `org.apache.kafka.clients.producer.Partitioner` 接口,同时在创建 `KafkaProducer` 实例时配置 `partitioner.class` 参数。

通常需要自定义分区一般是在想尽量的保证消息的顺序性。
或者是写入某些特有的分区,由特别的消费者来进行处理等。
#### 默认策略
最后一种则是默认的路由策略,如果我们啥都没做就会执行该策略。
该策略也会使得消息分配的比较均匀。
来看看它的实现:

简单的来说分为以下几步:
- 获取 Topic 分区数。
- 将内部维护的一个线程安全计数器 +1。
- 与分区数取模得到分区编号。
其实这就是很典型的轮询算法,所以只要分区数不频繁变动这种方式也会比较均匀。
### 写入内部缓存
在 `send()` 方法拿到分区后会调用一个 `append()` 函数:

该函数中会调用一个 `getOrCreateDeque()` 写入到一个内部缓存中 `batches`。

### 消费缓存
在最开始初始化的 IO 线程其实是一个守护线程,它会一直消费这些数据。

通过图中的几个函数会获取到之前写入的数据。这块内容可以不必深究,但其中有个 `completeBatch` 方法却非常关键。

调用该方法时候肯定已经是消息发送完毕了,所以会调用 `batch.done()` 来完成之前我们在 `send()` 方法中定义的回调接口。

> 从这里也可以看出为什么之前说发送完成后元数据和异常信息只会出现一个。
# Producer 参数解析
发送流程讲完了再来看看 `Producer` 中比较重要的几个参数。
## acks
`acks` 是一个影响消息吞吐量的一个关键参数。

主要有 `[all、-1, 0, 1]` 这几个选项,默认为 1。
由于 `Kafka` 不是采取的主备模式,而是采用类似于 Zookeeper 的主备模式。
> 前提是 `Topic` 配置副本数量 `replica > 1`。
当 `acks = all/-1` 时:
意味着会确保所有的 follower 副本都完成数据的写入才会返回。
这样可以保证消息不会丢失!
> 但同时性能和吞吐量却是最低的。
当 `acks = 0` 时:
producer 不会等待副本的任何响应,这样最容易丢失消息但同时性能却是最好的!
当 `acks = 1` 时:
这是一种折中的方案,它会等待副本 Leader 响应,但不会等到 follower 的响应。
一旦 Leader 挂掉消息就会丢失。但性能和消息安全性都得到了一定的保证。
## batch.size
这个参数看名称就知道是内部缓存区的大小限制,对他适当的调大可以提高吞吐量。
但也不能极端,调太大会浪费内存。小了也发挥不了作用,也是一个典型的时间和空间的权衡。


上图是几个使用的体现。
## retries
`retries` 该参数主要是来做重试使用,当发生一些网络抖动都会造成重试。
这个参数也就是限制重试次数。
但也有一些其他问题。
- 因为是重发所以消息顺序可能不会一致,这也是上文提到就算是一个分区消息也不会是完全顺序的情况。
- 还是由于网络问题,本来消息已经成功写入了但是没有成功响应给 producer,进行重试时就可能会出现`消息重复`。这种只能是消费者进行幂等处理。
# 高效的发送方式
如果消息量真的非常大,同时又需要尽快的将消息发送到 `Kafka`。一个 `producer` 始终会收到缓存大小等影响。
那是否可以创建多个 `producer` 来进行发送呢?
- 配置一个最大 producer 个数。
- 发送消息时首先获取一个 `producer`,获取的同时判断是否达到最大上限,没有就新建一个同时保存到内部的 `List` 中,保存时做好同步处理防止并发问题。
- 获取发送者时可以按照默认的分区策略使用轮询的方式获取(保证使用均匀)。
这样在大量、频繁的消息发送场景中可以提高发送效率减轻单个 `producer` 的压力。
# 关闭 Producer
最后则是 `Producer` 的关闭,Producer 在使用过程中消耗了不少资源(线程、内存、网络等)因此需要显式的关闭从而回收这些资源。

默认的 `close()` 方法和带有超时时间的方法都是在一定的时间后强制关闭。
但在过期之前都会处理完剩余的任务。
所以使用哪一个得视情况而定。
# 总结
本文内容较多,从实例和源码的角度分析了 Kafka 生产者。
希望看完的朋友能有收获,同时也欢迎留言讨论。
不出意外下期会讨论 Kafka 消费者。
> 如果对你有帮助还请分享让更多的人看到。
**欢迎关注公众号一起交流:**
<img src="https://ws2.sinaimg.cn/large/006tKfTcly1fsa01u7ro1j30gs0howfq.jpg" width="300"/>
================================================
FILE: MD/newObject.md
================================================
# 对象的创建与内存分配
## 创建对象
当 `JVM` 收到一个 `new` 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被[加载](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ClassLoad.md)过了,如果没有的话则要进行一次类加载。
接着就是分配内存了,通常有两种方式:
- 指针碰撞
- 空闲列表
使用指针碰撞的前提是堆内存是**完全工整**的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可。
当堆中已经使用的内存和未使用的内存**互相交错**时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式。虚拟机会维护一个空闲的列表,用于记录哪些内存是可以进行分配的,分配时直接从可用内存中直接分配即可。
堆中的内存是否工整是有**垃圾收集器**来决定的,如果带有压缩功能的垃圾收集器就是采用指针碰撞的方式来进行内存分配的。
分配内存时也会出现并发问题:
这样可以在创建对象的时候使用 `CAS` 这样的乐观锁来保证。
也可以将内存分配安排在每个线程独有的空间进行,每个线程首先在堆内存中分配一小块内存,称为本地分配缓存(`TLAB : Thread Local Allocation Buffer`)。
分配内存时,只需要在自己的分配缓存中分配即可,由于这个内存区域是线程私有的,所以不会出现并发问题。
可以使用 `-XX:+/-UseTLAB` 参数来设定 `JVM` 是否开启 `TLAB` 。
内存分配之后需要对该对象进行设置,如对象头。对象头的一些应用可以查看 [Synchronize 关键字原理](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Synchronize.md)。
### 对象访问
一个对象被创建之后自然是为了使用,在 `Java` 中是通过栈来引用堆内存中的对象来进行操作的。
对于我们常用的 `HotSpot` 虚拟机来说,这样引用关系是通过直接指针来关联的。
如图:

这样的好处就是:在 Java 里进行频繁的对象访问可以提升访问速度(相对于使用句柄池来说)。
## 内存分配
### Eden 区分配
简单的来说对象都是在堆内存中分配的,往细一点看则是优先在 `Eden` 区分配。
这里就涉及到堆内存的划分了,为了方便垃圾回收,JVM 将堆内存分为新生代和老年代。
而新生代中又会划分为 `Eden` 区,`from Survivor、to Survivor` 区。
其中 `Eden` 和 `Survivor` 区的比例默认是 `8:1:1`,当然也支持参数调整 `-XX:SurvivorRatio=8`。
当在 `Eden` 区分配内存不足时,则会发生 `minorGC` ,由于 `Java` 对象多数是**朝生夕灭**的特性,所以 `minorGC` 通常会比较频繁,效率也比较高。
当发生 `minorGC` 时,JVM 会根据[复制算法](https://github.com/crossoverJie/Java-Interview/blob/master/MD/GarbageCollection.md#%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95)将存活的对象拷贝到另一个未使用的 `Survivor` 区,如果 `Survivor` 区内存不足时,则会使用分配担保策略将对象移动到老年代中。
谈到 `minorGC` 时,就不得不提到 `fullGC(majorGC)` ,这是指发生在老年代的 `GC` ,不论是效率还是速度都比 `minorGC` 慢的多,回收时还会发生 `stop the world` 使程序发生停顿,所以应当尽量避免发生 `fullGC` 。
### 老年代分配
也有一些情况会导致对象直接在老年代分配,比如当分配一个大对象时(大的数组,很长的字符串),由于 `Eden` 区没有足够大的连续空间来分配时,会导致提前触发一次 `GC`,所以尽量别频繁的创建大对象。
因此 `JVM` 会根据一个阈值来判断大于该阈值对象直接分配到老年代,这样可以避免在新生代频繁的发生 `GC`。
对于一些在新生代的老对象 `JVM` 也会根据某种机制移动到老年代中。
JVM 是根据记录对象年龄的方式来判断该对象是否应该移动到老年代,根据新生代的复制算法,当一个对象被移动到 `Survivor` 区之后 JVM 就给该对象的年龄记为1,每当熬过一次 `minorGC` 后对象的年龄就 +1 ,直到达到阈值(默认为15)就移动到老年代中。
> 可以使用 `-XX:MaxTenuringThreshold=15` 来配置这个阈值。
## 总结
虽说这些内容略显枯燥,但当应用发生不正常的 `GC` 时,可以方便更快的定位问题。
================================================
FILE: MD/soft-skills/Interview-experience.md
================================================

## 前言
最近有些朋友在面试阿里,加上 [Java-Interview](https://github.com/crossoverJie/Java-Interview) 项目的原因也有小伙伴和我讨论,近期也在负责部门的招聘,这让我想起年初那段长达三个月的奇葩面试经历🤣。
本来没想拿出来说的,毕竟最后也没成。
但由于那几个月的经历让我了解到了大厂的工作方式、对候选同学的考察重点以及面试官的套路等都有了全新的认识。
当然最重要的是这段时间的查漏补缺也让自己精进不少。
先交代下背景吧:
从去年 12 月到今年三月底,我前前后后面了阿里三个部门。
其中两个部门通过了技术面试,还有一个跪在了三面。
光看结果还不错,但整个流程堪称曲折。
下面我会尽量描述流程以及大致的面试题目大纲,希望对想要跳槽、正在面试的同学带来点灵感,帮助可能谈不上,但启发还是能有。
以下内容较长,请再次备好瓜子板凳。
<!--more-->
## A 部门
首先是第一次机会,去年 12 月份有位大佬加我,后来才知道是一个部门的技术 Leader 在网上看到我的博客,问我想不想来阿里试试。
这时距离上次面阿里也过去一年多了,也想看看现在几斤几两,于是便同意了。
在推荐一周之后收到了杭州打来的电话,说来也巧,那时候我正在机场候机,距离登记还有大概一个小时,心想时间肯定够了。
那是我时隔一年多第一次面试,还是在机场这样嘈杂的环境里。多多少少还是有些紧张。
### 一面
以下是我印象比较深刻的内容:
**面试官:**
谈谈你做过项目中印象较深或自认为做的比较好的地方?
**博主:**
我觉得我在 XX 做的不错,用了 XX 需求实现 XX 功能,性能提高了 N 倍。
**面试官:**
你说使用到了 AOP ,能谈谈它的实现原理嘛?
**博主:**
它是依靠动态代理实现的,动态代理又分为 JDK 自身的以及 CGLIB 。。。。
**面试官:**
嗯,能说说他们的不同及优缺点嘛?
**博主:**
JDK 是基于接口实现,而 CGLIB 继承代理类。。。
就是这样会一直问下去,如果聊的差不多了就开始问一些零散的问题:
- JMM 内存模型,如何划分的?分别存储什么内容?线程安全与否?
- 类加载机制,谈到双亲委派模型后会问到哪些违反了双亲委派模型?为什么?为什么要双亲委派?好处是什么?
- 平时怎么使用多线程?有哪些好处?线程池的几个核心参数的意义?
- 线程间通信的方式?
- HashMap 的原理?当谈到线程不安全时自然引申出 ConcurrentHashMap ,它的实现原理?
- 分库分表如何设计?垂直拆分、水平拆分?
- 业务 ID 的生成规则,有哪些方式?
- SQL 调优?平时使用数据库有哪些注意点?
- 当一个应用启动缓慢如何优化?
大概是以上这些,当聊到倒数第二个时我已经登机了。最后不得不提前挂断,结束之前告诉我之后会换一个同事和我沟通,听到这样的回复一面应该是过了,
后面也确实证实了这点。
### 二面
大概过了一周,二面如期而至。
我听声音很熟,就尝试问下是不是之前一面的面试官,结果真是。
由于二面的面试官临时有事所以他来替一下。于是我赶紧问他能否把之前答的不好的再说说?的到了肯定的答复后开始了我的表演。
有了第一次的经验这一次自然也轻车熟路,原本感觉一切尽在掌握却被告知需要笔试突然被激醒。
笔试是一个在线平台,需要在网页中写代码,会有一个明确的题目:
> 从一个日志文件中根据关键字读取日志,记录出现的次数,最后按照次数排序打印。
在这过程中切记要和面试官多多交流,因为笔试有时间限制,别到最后发现题目理解错了,这就和高考作文写完发现方向错了一样要命。
而且在沟通过程中体现出你解题的思路,即使最终结果不对,但说不定思考的过程很符合面试官的胃口哦。这也和今年的高考改卷一样;过程正确得高分,只有结果得低分。
### 三面
又过了差不多一周的时间接到了三面的电话,一般到了三面会是技术 Leader 之类的角色。
这个过程中不会过多强调技术细节,更多的考察软件能,比如团队协作、学习能力等。
但我记得也问了以下一些技术问题:
- 谈谈你所理解的 HTTP 协议?
- 对 TCP 的理解?三次握手?滑动窗口?
- 基本算法,Base64 等。
- Java 内存模型,Happen Before 的理解。
一周之后我接到了 HR 助理的电话约了和 HRBP 以及产品技术负责人的视频面试。
但是我却没有面下去,具体原因得往下看。
## B 部门
在 A 部门三面完成后,我等了差不多一星期,这期间我却收到了一封邮件。
大概内容是他在 GitHub 上看到的我,他们的技术总监对我很感兴趣(我都不敢相信我的眼镜),问我想不想来阿里试试。
我对比了 A B 部门的区别发现 B 部门在做的事情上确实更加有诱惑力,之后我表达了有一个面试正在流程中的顾虑;对方表示可以私下和我快速的进行三面,如果一切没问题再交由我自行选择。至少对双方都是一个双赢嘛。
我想也不亏,并且对方很有诚意,就答应试试;于是便有了下面的面试:
### 一面
**面试官:**
对 Java 锁的理解?
**博主:**
我谈到了 synchronize,Lock 接口的应用。
**面试官:**
他们两者的区别以及优缺点呢?
**博主:**
`synchronize` 在 JDK1.6 之前称为重量锁,是通过进出对象监视器来实现同步的;1.6 之后做了 XX 优化。。。
而 `ReentrantLock` 是利用了一个巧妙数据结构实现的,并且加锁解锁是显式的。。。
之后又引申到[分布式锁](https://crossoverjie.top/%2F2018%2F03%2F29%2Fdistributed-lock%2Fdistributed-lock-redis%2F),光这块就聊了差不多半个小时。
之后又聊到了我的[开源项目](https://github.com/crossoverJie):
- 是如何想做这个项目的?
- 已经有一些关注了后续是如何规划的?
- 你今后的学习计划是什么?
- 平时看哪些书?
之后技术聊的不是很多,但对于个人发展却聊了不少。
> 关于锁相关的内容可以参考这里:[ReentrantLock 实现原理](https://crossoverjie.top/%2F2018%2F01%2F25%2FReentrantLock%2F) [synchronize 关键字原理](https://crossoverjie.top/%2F2018%2F01%2F14%2FSynchronize%2F)
### 二面
隔了差不多一天的时间,二面很快就来了。
内容不是很多:
- [线程间通信的多种方式](https://crossoverjie.top/%2F2018%2F03%2F16%2Fjava-senior%2Fthread-communication%2F)?
- 限流算法?单机限流?分布式限流?
- 提到了 Guava Cache ,了解它的[实现原理](https://crossoverjie.top/2018/06/13/guava/guava-cache/)嘛?
- 如何定位一个线上问题?
- CPU 高负载?OOM 排查等?
聊完之后表示第二天应该会有三面。
### 三面
三面的面试官应该是之前邮件中提到的那位总监大佬,以前应该也是一线的技术大牛;聊的问题不是很多:
- 谈谈对 Netty 的理解?
- Netty 的线程模型?
- [写一个 LRU 缓存](https://crossoverjie.top/2018/04/07/algorithm/LRU-cache/)。
### 笔试
本以为技术面试完了,结果后面告知所有的面试流程都得有笔试了,于是又参与了一次笔试:
> [交替打印奇偶数](https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/actual/TwoThread.java)
这个相对比较简单,基于锁、等待唤醒机制都是可以的。最后也告知笔试通过。
之后在推荐我的那位大佬的帮助下戏剧般的通过了整个技术轮(真的很感谢他的认可),并且得知这个消息是在我刚好和 A 部门约好视频面试时间之后。
也就意味着我必须**拒掉一个部门!**
没看错,是我要拒掉一个。这对我来说确实太难了,我压根没想过还有两个机会摆在我面前。
最后凭着个人的爱好以及 B 部门的热情我很不好意思的拒掉了 A 部门。。。
### HR 面
在面这之前我从来没有面过这样大厂的 HR 流程,于是疯狂搜索,希望能弥补点经验。
也许这就是乐极生悲吧,我确实猜中了 HR 问的大部分问题,但遗憾的是最终依然没能通过。
后来我在想如果我没有拒掉 A ,会不会结局不一样了?
但现实就是如此,没有那么多假设,并且每个人也得为自己的选择负责!
大概的问题是:
- 为什么想来阿里?
- 个人做的最成功最有挑战的事情是什么?
- 工作中最难忘的经历?
- 对加入我们团队有何期待?
## C 部门
HR 这关被 Pass 之后没多久我居然又收到了第三个部门的邀约。
说实话当时我是拒绝的,之前经历了将近两个月的时间却没能如愿我内心是崩溃的。
我向联系我的大佬表达了我的想法,他倒觉得我最后被 pass 的原因是个小问题,再尝试的话会有很大的几率通过。
我把这事给朋友说了之后也支持我再试试,反正也没啥损失嘛,而且面试的状态还在。
所以我又被打了鸡血,才有了下面的面试经过:
### 一面
**面试官:**
服务化框架的选型和差异?
**博主:**
一起探讨了 SpringCloud、Dubbo、Thrift 的差异,优缺点等。
**面试官:**
[一致性 Hash 算法的原理](https://crossoverjie.top/2018/01/08/Consistent-Hash/)?
**博主:**
将数据 Hash 之后落到一个 `0 ~ 2^32-1` 构成的一个环上。。。。
**面试官:**
谈谈你理解的 Zookeeper?
**博主:**
作为一个分布式协调器。。。
**面试官:**
如何处理 MQ 重复消费?
**博主:**
业务幂等处理。。。。
**面试官:**
客户端负载算法?
**博主:**
轮询、随机、一致性 Hash、故障转移、LRU 等。。
**面试官:**
long 类型的赋值是否是原子的?
**博主:**
不是。。。
**面试官:**
[volatile 关键字的原理及作用?happen Before?](https://crossoverjie.top/2018/03/09/volatile/)
**博主:**
可见性、一致性。。
### 二面
一面之后大概一周的时间接到了二面的电话:
原以为会像之前一样直接进入笔试,这次上来先简单聊了下:
- 谈谈对微服务的理解,好处以及弊端?
- 分布式缓存的设计?热点缓存?
之后才正式进入笔试流程:
> 这次主要考察设计能力,其实就是对设计模式的理解?能否应对后续的扩展性。
笔试完了之后也和面试官交流,原以为会是算法之类的测试,后来得知他能看到前几轮的笔试情况,特地挑的没有做过的方向。
所以大家也不用刻意去押题,总有你想不到的,平时多积累才是硬道理。
### 三面
又过了两周左右,得到 HR 通知;希望能过去杭州参加现场面试。并且阿里包了来回的机票酒店等。
可见阿里对人才渴望还是舍得下成本的。
既然都这样了,就当成一次旅游所以去了一趟杭州。
现场面的时候有别于其他面试,是由两个面试官同时参与:
> 给一个场景,谈谈你的架构方式。
这就对平时的积累要求较高了。
还有一个印象较深的是:
> 在网页上点击一个按钮到服务器的整个流程,尽量完整。
其实之前看过,好像是 Google 的一个面试题。
完了之后让我回去等通知,没有见到 HR 我就知道凉了,果不其然。
## 总结
看到这里的朋友应该都是老铁了,我也把上文提到的大多数面试题整理在了 GitHub:

厂库地址:
[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
最后总结下这将近四个月的面试心得:
- 一定要积极的推销自己,像在 A 部门的三面时,由于基础答得不是很好;所以最后我表达了自己的态度,对工作、技术的积极性。让面试官看到你的潜力值得一个 HC 名额。
- 面试过程中遇到自己的不会的可以主动提出,切不可不懂装懂,这一问就露馅。可以将面试官引导到自己擅长的领域。比如当时我正好研究了锁,所以和面试官一聊就是半小时这就是加分项。
- 平时要主动积累知识。写博客和参与开源项目就是很好的方式。
- 博客可以记录自己踩过的坑,加深印象,而且在写的过程中可以查漏补缺,最后把整个知识体系巩固的比较牢固,良好的内容还可以得到意想不到的收获,比如我第一次面试的机会。
- GitHub 是开发者的一张名片,积极参与开源项目可以和全球大佬头脑风暴,并且在面试过程中绝对是一个加分利器。
- 面试官一般最后都会问你有什么要问我的?千万不要问一些公司福利待遇之类的问题。可以问下本次面试的表现?还有哪些需要完善的?从而知道自己答得如何也能补全自己。
还有一点:不要在某次面试失利后否定自己,有时真的不是自己能力不行。这个也讲缘分。
**塞翁失马焉知非福**
我就是个例子,虽然最后没能去成阿里,现在在公司也是一个部门的技术负责人,在我们城市还有个窝,温馨的家,和女朋友一起为想要的生活努力奋斗。
> 欢迎关注作者公众号于我交流🤗。
================================================
FILE: MD/soft-skills/how-to-be-developer.md
================================================

## 前言
已经记不清有多少读者问过:
> 博主,你是怎么学习的?像我这样的情况有啥好的建议嘛?
也不知道啥时候我居然成人生导师了。当然我不排斥这些问题,和大家交流都是学习的过程。
因此也许诺会准备一篇关于学习方面的文章;所以本文其实准备了很久,篇幅较长,大家耐心看完希望能有收获。
> 以下内容仅代表我从业以来所积累的相关经验,我会从硬技能、软实力这些方面尽量阐述我所认为的 `“不那么差的程序员”` 应当做到哪些技能。
<!--more-->
## 技能树
作为一名码代码的技术工人,怎么说干的还是技术活。
既然是技术活那专业实力就得过硬,下面我会按照相关类别谈谈我们应该掌握哪些。
### 计算机基础
一名和电脑打交道的工种,计算机是我们赖以生存的工具。所以一些基础技能是我们应该和必须掌握的。
> 比如网络相关的知识。
其中就包含了 TCP 协议,它和 UDP 的差异。需要理解 TCP 三次握手的含义,[拆、粘包](http://t.cn/RDYBny8)等问题。
当然上层最常见的 HTTP 也需要了解,甚至是熟悉。
这块推荐[《图解 HTTP》](https://book.douban.com/subject/25863515/)一书。
> 接着是操作系统相关知识。
由于工作后你写的大部分代码都是运行在 Linux 服务器上,所以对于这个看它脸色行事主你也得熟悉才行。
比如进程、线程、内存等概念;服务器常见的命令使用,这个没啥窍门就是得平时多敲敲多总结。
我也是之前兼职了半年运维才算是对这一块比较熟悉。
Linux 这个自然是推荐业界非常出名的[《鸟哥的 Linux 私房菜》](https://book.douban.com/subject/4889838/)。
当作为一个初学者学习这些东西时肯定会觉得枯燥乏味,大学一般在讲专业课之前都会有这些基础学科。我相信大部分同学应该都没怎么仔细听讲,因为确实这些东西就算是学会了记熟了也没有太多直接的激励。
但当你工作几年之后会发现,只要你还在做计算机相关的工作,这些都是绕不开的,当哪天这些知识不经意的帮助到你时你会庆幸当初正确的选择。
### 数据结构与算法
接下来会谈到另一门枯燥的课程:数据结构。
这块当初在大学时也是最不受待见的一门课程,也是我唯一挂过的科目。
记得当时每次上课老师就让大家用 C 语言练习书上的习题,看着一个个拆开都认识的字母组合在一起就六亲不认我果断选择了放弃。
这也造成现在的我每隔一段时间就要看二叉树、红黑树、栈、队列等知识,加深印象。
算法这个东西我确实没有啥发言权,之前坚持刷了部分 [LeetCode](https://github.com/crossoverJie/leetcode) 的题目也大多停留在初中级。
但像基本的查找、排序算法我觉得还是要会的,不一定要手写出来但要理解其思路。
所以**强烈建议**还在大学同学们积极参与一些 ACM 比赛,绝对是今后的加分利器。
这一块内容可能会在应届生校招时发挥较大作用,在工作中如果你的本职工作是 `Java Web` 开发的话,这一块涉猎的几率还是比较低。
不过一旦你接触到了模型设计、中间件、高效存储、查询等内容这些也是绕不过的坎。
这块内容和上面的计算机基础差不多,对于我们 Java 开发来说我觉得平时除了多刷刷 LeetCode 加深印象之外,在日常开发中每选择一个容器存放数据时想想为什么选它?有没有更好的存储方式?写入、查询效率如何?
同样的坚持下去,今后肯定收货颇丰。
同时推荐[《算法(第4版)》](https://book.douban.com/subject/19952400/)
### Java 基础
这里大部分的读者都是 Java 相关,所以这个强相关的技能非常重要。
Java 基础则是走向 Java 高级的必经之路。
这里抛开基本语法不谈,重点讨论实际工作中高频次的东西。
- 基本容器,如:HashMap、ArrayList、HashSet、LinkedList 等,不但要会用还得了解其中的原理。这样才能在不同的场景选择最优的设计。
- IO、NIO 也是需要掌握。日常开发中大部分是在和磁盘、网络(写日志、数据库、Redis)打交道,这些都是 IO 的过程。
- 常见的设计模式如:代理、工厂、回调、构建者模式,这对开发灵活、扩展性强的应用有很大帮助。
- Java 多线程是非常重要的特性,日常开发很多。能理解线程模型、多线程优缺点、以及如何避免。
- 良好的单测习惯,很多人觉得写单测浪费时间没有意义。但正是有了单测可以提前暴露出许多问题,减少测试返工几率,提高代码质量。
- 良好的编程规范,这个可以参考《阿里巴巴 Java 开发手册》以及在它基础上优化的[《唯品会 Java 手册》](https://vipshop.github.io/vjtools/#/standard/)
> [《Java核心技术·卷 I》](https://book.douban.com/subject/26880667/)值得推荐。
### 多线程应用
有了扎实的基础之后来谈谈多线程、并发相关的内容。
想让自己的 title 里加上“高级”两字肯定得经过并发的洗礼。
> 这里谈论的并发主要是指单应用里的场景,多应用的可以看后文的分布式内容。
多线程的出现主要是为了提高 CPU 的利用率、任务的执行效率。但并不是用了多线程就一定能达到这样的效果,因为它同时也带来了一些问题:
- 上下文切换
- 共享资源
- 可见性、原子性、有序性等。
一旦使用了多线程那肯定会比单线程的程序要变得复杂和不可控,甚至使用不当还会比单线程慢。所以要考虑清楚是否真的需要多线程。
会用了之后也要考虑为啥多线程会出现那样的问题,这时就需要理解内存模型、可见性之类的知识点。
同样的解决方式又有哪些?各自的优缺点也需要掌握。
谈到多线程就不得不提并发包下面的内容 `java.util.concurrent`。
最常用及需要掌握的有:
- 原子类:用于并发场景的原子操作。
- 队列。常用于解耦,需要了解其实现原理。
- 并发工具,如 [ConcurrentHashMap](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)、[CountDownLatch](https://crossoverjie.top/%2F2018%2F03%2F16%2Fjava-senior%2Fthread-communication%2F#CountDownLatch-%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7) 之类的工具使用以及原理。
- [线程池使用](https://crossoverjie.top/2018/07/29/java-senior/ThreadPool/),以及相关原理。
- 锁相关内容:[synchronized](https://crossoverjie.top/2018/01/14/Synchronize/)、[ReentrantLock](https://crossoverjie.top/2018/01/25/ReentrantLock/) 的使用及原理。
这一块的内容可以然我们知道写 JDK 大牛处理并发的思路,对我们自己编写高质量的多线程程序也有很多帮助。
推荐[《Java 并发编程的艺术》](https://book.douban.com/subject/26591326/)很好的并发入门书籍。
### JVM 虚拟机
想要深入 Java ,JVM 是不可或缺的。对于大部分工作 1~3 年的开发者来说直接接触这一些内容是比较少的。
到了 3~5 年这个阶段就必须得了解了,以下内容我觉得是必须要掌握的:
- JVM 内存划分,[知道哪块内存存放哪些内容](https://crossoverjie.top/%2F2018%2F01%2F18%2FnewObject%2F);线程安全与否;内存不够怎么处理等。
- 不同情况的[内存溢出、栈溢出](https://github.com/crossoverJie/Java-Interview/blob/master/MD/OOM-analysis.md#oom-%E5%88%86%E6%9E%90),以及定位解决方案。
- [分代的垃圾回收策略。](https://github.com/crossoverJie/Java-Interview/blob/master/MD/GarbageCollection.md)
- [线上问题定位及相关解决方案](https://crossoverjie.top/2018/07/08/java-senior/JVM-Troubleshoot/)。
- 一个类的加载、创建对象、垃圾回收、类卸载的整个过程。
掌握这些内容真的对实际分析问题起到巨大帮助。
> 对此强力推荐[《深入理解Java虚拟机](https://book.douban.com/subject/24722612/)》,这本书反反复复看过好几遍,每个阶段阅读都有不同的收获。
### 数据库
做 WEB 应用开发的同学肯定要和数据库打不少交道,而且通常来说一个系统最先出现瓶颈往往都是数据库,说数据库是压到系统的最后一根稻草一点也不为过。
所以对数据库的掌握也是非常有必要。拿互联网用的较多的 MySQL 数据库为例,一些必须掌握的知识点:
- 索引的数据结构及原理、哪些字段应当创建索引。
- 针对于一个慢 SQL 的优化思路。
- 数据库水平垂直拆分的方案,需要了解业界常用的 MyCAT、sharding-sphere 等中间件。
常规使用可以参考《阿里巴巴 Java 开发手册》中的数据库章节,想要深入了解 MySQL 那肯定得推荐经典的[《高性能 MySQL》](https://book.douban.com/subject/23008813/)一书了。
### 分布式技术
随着互联网的发展,传统的单体应用越来越不适合现有场景。
因此分布式技术出现了,这块涵盖的内容太多了,经验有限只能列举我日常使用到的一些内容:
- 首先是一些基础理论如:CAP 定理,知道分布式系统会带来的一些问题以及各个应用权衡的方式。
- 了解近些年大热的微服务相关定义、来源以及对比,有条件的可以阅读 `martin fowler` 的原文 [Microservices](https://martinfowler.com/articles/microservices.html),或者也可以搜索相关的国内翻译。
- 对 Dubbo、SpringCloud 等分布式框架的使用,最好是要了解原理。
- 接着要对分布式带来的问题提出解决方案。如[分布式锁](https://crossoverjie.top/2018/03/29/distributed-lock/distributed-lock-redis/)、[分布式限流](https://crossoverjie.top/2018/04/28/sbc/sbc7-Distributed-Limit/)、分布式事务、[分布式缓存](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Cache-design.md)、分布式 ID、消息中间件等。
- 也要了解一些分布式中的负载算法:权重、Hash、一致性 Hash、故障转移、[LRU](https://crossoverjie.top/2018/04/07/algorithm/LRU-cache/) 等。
- 最好能做一个实践如:[秒杀架构实践
](https://crossoverjie.top/%2F2018%2F05%2F07%2Fssm%2FSSM18-seconds-kill%2F)
之前有开源一个分布式相关解决组件:
[https://github.com/crossoverJie/distributed-redis-tool](https://github.com/crossoverJie/distributed-redis-tool)
同时推荐一本入门科普[《大型网站技术架构》](https://book.douban.com/subject/25723064/),出版时间有点早,从中可以学习一些思路。
### 懂点架构
相信大家都有一个架构师的梦想。
架构师给人的感觉就是画画图纸,搭好架子,下面的人员来添砖加瓦最终产出。
但其实需要的内功也要非常深厚,就上面列举的样样需要掌握,底层到操作系统、算法;上层到应用、框架都需要非常精通。(PPT 架构师除外)
我自身参与架构经验也不多,所以只能提供有限的建议。
首先分布式肯定得掌握,毕竟现在大部分的架构都是基于分布式的。
这其中就得根据 CAP 理论结合项目情况来选择一致性还是可用性,同时如何做好适合现有团队的技术选型。
这里推荐下开涛老师的[《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/),列举了很多架构实例,不过网上褒贬不一,但对于刚入门架构的能科普不少知识。
## 如何学习
谈完了技能树,现在来聊聊如何学习,这也是被问的最多的一个话题。
而关于学习讨论的最多的也是看视频还是看书?
### 视频
不得不承认视频是获取知识最便捷的来源,毕竟包含了图、文、声。
大学几年时间其实我也没好好上专业课,我记得真正入门 Java 还是一个暑假花了两个月的时间天天在家里看 ”马士兵“ 老师的视频教程,当时的资源也很老了,记得好像是 07 年出的视频(用的还是 Google )。
那段时间早起晚睡,每天学到东西之后马上实践,心里也很有成就感。后来开学之后一度成为同学们眼中的”学霸“人物。
> 现在打开我 12 年的电脑,硬盘里还躺着好几十 G 的教学视频。
### 看书
工作后时间真的很宝贵,完全没有了学生生涯的想学就学的自由。所以现在我主要知识来源还是书籍。
这些是我最近看的书:

看书又会涉及到电子书和纸质书的区别,我个人比较喜欢纸质书。毕竟我可以方便的记笔记以及可以随时切换章节。最主要的还是从小养成的闻书香的习惯。
### 知识付费
近几年知识付费越来越流行,许多大佬也加入了这个行列,人们也逐渐在习惯为知识去付费。
说实话写一好篇文章出一份视频都非常不容易,能有正向的激励,作者才能持续输出更好的内容。
这块我觉得国内做的比较好我也为之付费的有极客时间、大佬的知识星球等。
这三点没有绝对的好坏之分,其实可以看出我刚入门的时候看视频,工作之后看书及知识付费内容。
视频的好处是可以跟着里面老师的思路一步一步往下走,比较有音视频代入感强,就像学校老师讲课一样。
但由于内容较长使读者没法知晓其中的重点,甚至都不敢快进生怕错过了哪个重要知识,现在由于 IT 越来越火,网上的视频也很多导致质量参差不齐也不成体系。
而看书可以选择性的浏览自己感兴趣的章节,费解的内容也方便反复阅读
所以建议刚入门的同学可以看看视频跟着学,参与工作一段时间后可以尝试多看看书。
当然这不是绝对的,找到适合自己的学习方式就好。但不管是视频还是看书都要多做多实践。
## 打造个人品牌
个人品牌看似很程序员这个职业不怎么沾边,但在现今的互联网时代对于每个人来说都很重要。
以往我们在写简历或是评估他人简历的时候往往不会想到去网络搜索他的个人信息,但在这个信息爆炸的时代你在网上留下的一点印记都能被发现。
### 博客
因此我们需要维护好自己的名片,比如先搭建自己的个人博客。
博客的好处我也谈过几次了,前期关注人少没关系,重要的是坚持,当你写到 50、100篇文章后你会发现自己在这过程中一定是的到了提高。
### GitHub
第二点就和技术人比较相关了:参与维护好自己的 GitHub。
由于 GitHub 的特殊属性,维护好后可以更好的打造个人品牌。
`Talk is cheap. Show me the code` 可不是随便说说的。
想要维护好可以从几个方面着手:
- 参与他人的项目,不管是代码库还是知识库都可以,先融入进社区。
- 发起自己的开源项目,不管是平时开发过程中的小痛点,还是精心整理的知识点都可以。
但这过程中有几点还是要注意:
- 我们需要遵守 GitHub 的社交礼仪。能用英文尽量就用英文,特别是在国外厂库中。
- 尽量少 push 一些与代码工作无关的内容,我认为这并不能提高自己的品牌。
- `别去刷 star`。这也是近期才流行起来,不知道为什么总有一些人会钻这种空子,刷起来的热度对自己并没有任何提高。
这里有一篇国外大佬写的 `How to build your personal brand as a new developer` :
[https://medium.freecodecamp.org/building-your-personal-brand-as-a-new-web-developer-f6d4150fd217](https://medium.freecodecamp.org/building-your-personal-brand-as-a-new-web-developer-f6d4150fd217)
## English 挺重要
再来谈谈英语的重要性,我记得刚上大学时老师以及一些培训机构都会说:
> 别怕自己英语差就学不了编程,真正常用的就那些词语。
这句话虽没错,但英语在对 IT 这行来说还是有着极大的加分能力。
拿常见的 JDK 里的源码注释也是纯英文的,如果英语还不错的话,一些 Spring 的东西完全可以自学,直接去 Spring 官网就可以查看,甚至后面出的 SpringCloud,官方资料就是最好的教程。
再有就是平时查资料时,有条件的可以尝试用 `Google + 英文` 搜索,你会发现新的世界。
不然也不会有面向 `Google/Stack Overflow` 编程。
对于英语好的同学自然不怕,那不怎么好的咋办呢?
比如我,但我在坚持以下几点:
- 所有的手机、电脑系统统统换成英语语言,养成习惯(不过也有尴尬的连菜单都找不到的情况)。
- 订阅一些英语周刊,比如 ”湾区日报“。
- 定期去类似于 [https://medium.com/](https://medium.com/) 这样具有影响力的国外社区阅读文章。
虽然现在我也谈不上多好,但目前我也在努力,希望大家也一起坚持。
推荐一本近期在看的书《程序员的英语》。
## 保持竞争力
技术这个行业发展迅速、变化太快,每年也都有无数相关行业毕业生加入竞争,稍不留神就会被赶上甚至超越。
所以我们无时无刻都得保持竞争力。
多的谈不上,我只能谈下目前我在做的事情:
- **打好基础**。不是学了之后就忘了,需要不停的去看,巩固,基础是万变不离其宗的。
- 多看源码,了解原理,不要停留在调参侠的境界。
- 关注行业发展、新技术、新动态至少不能落伍了。
- 争取每周产出一篇技术相关文章。
- 积极参与开源项目。
## 思维导图

结合上文产出了一个思维导图更直观些。
## 总结
本文结合了自身的一些经验列举了一些方法,不一定对每位都有效需要自行判断。
也反反复复写了差不多一周的时间,希望对在这条路上和正在路上的朋友们起到一些作用。
大部分都只是谈了个思路,其实每一项单聊都能写很多。每个点都有推荐一本书籍,有更好建议欢迎留言讨论。
上文大部分的知识点都有维护在 GitHub 上,感兴趣的朋友可以自行查阅:

[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
================================================
FILE: MD/spring/spring-bean-lifecycle.md
================================================
## Spring Bean 生命周期
### 前言
Spring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。
首先看下生命周期图:

再谈生命周期之前有一点需要先明确:
> Spring 只帮我们管理单例模式 Bean 的**完整**生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。
### 注解方式
在 bean 初始化时会经历几个阶段,首先可以使用注解 `@PostConstruct`, `@PreDestroy` 来在 bean 的创建和销毁阶段进行调用:
```java
@Component
public class AnnotationBean {
private final static Logger LOGGER = LoggerFactory.getLogger(AnnotationBean.class);
@PostConstruct
public void start(){
LOGGER.info("AnnotationBean start");
}
@PreDestroy
public void destroy(){
LOGGER.info("AnnotationBean destroy");
}
}
```
### InitializingBean, DisposableBean 接口
还可以实现 `InitializingBean,DisposableBean` 这两个接口,也是在初始化以及销毁阶段调用:
```java
@Service
public class SpringLifeCycleService implements InitializingBean,DisposableBean{
private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleService.class);
@Override
public void afterPropertiesSet() throws Exception {
LOGGER.info("SpringLifeCycleService start");
}
@Override
public void destroy() throws Exception {
LOGGER.info("SpringLifeCycleService destroy");
}
}
```
### 自定义初始化和销毁方法
也可以自定义方法用于在初始化、销毁阶段调用:
```java
@Configuration
public class LifeCycleConfig {
@Bean(initMethod = "start", destroyMethod = "destroy")
public SpringLifeCycle create(){
SpringLifeCycle springLifeCycle = new SpringLifeCycle() ;
return springLifeCycle ;
}
}
public class SpringLifeCycle{
private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycle.class);
public void start(){
LOGGER.info("SpringLifeCycle start");
}
public void destroy(){
LOGGER.info("SpringLifeCycle destroy");
}
}
```
以上是在 SpringBoot 中可以这样配置,如果是原始的基于 XML 也是可以使用:
```xml
<bean class="com.crossoverjie.spring.SpringLifeCycle" init-method="start" destroy-method="destroy">
</bean>
```
来达到同样的效果。
### 实现 *Aware 接口
`*Aware` 接口可以用于在初始化 bean 时获得 Spring 中的一些对象,如获取 `Spring 上下文`等。
```java
@Component
public class SpringLifeCycleAware implements ApplicationContextAware {
private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleAware.class);
private ApplicationContext applicationContext ;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext ;
LOGGER.info("SpringLifeCycleAware start");
}
}
```
这样在 `springLifeCycleAware` 这个 bean 初始化会就会调用 `setApplicationContext` 方法,并可以获得 `applicationContext` 对象。
### BeanPostProcessor 增强处理器
实现 BeanPostProcessor 接口,Spring 中所有 bean 在做初始化时都会调用该接口中的两个方法,可以用于对一些特殊的 bean 进行处理:
```java
@Component
public class SpringLifeCycleProcessor implements BeanPostProcessor {
private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleProcessor.class);
/**
* 预初始化 初始化之前调用
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("annotationBean".equals(beanName)){
LOGGER.info("SpringLifeCycleProcessor start beanName={}",beanName);
}
return bean;
}
/**
* 后初始化 bean 初始化完成调用
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("annotationBean".equals(beanName)){
LOGGER.info("SpringLifeCycleProcessor end beanName={}",beanName);
}
return bean;
}
}
```
执行之后观察结果:
```
018-03-21 00:40:24.856 [restartedMain] INFO c.c.s.p.SpringLifeCycleProcessor - SpringLifeCycleProcessor start beanName=annotationBean
2018-03-21 00:40:24.860 [restartedMain] INFO c.c.spring.annotation.AnnotationBean - AnnotationBean start
2018-03-21 00:40:24.861 [restartedMain] INFO c.c.s.p.SpringLifeCycleProcessor - SpringLifeCycleProcessor end beanName=annotationBean
2018-03-21 00:40:24.864 [restartedMain] INFO c.c.s.aware.SpringLifeCycleAware - SpringLifeCycleAware start
2018-03-21 00:40:24.867 [restartedMain] INFO c.c.s.service.SpringLifeCycleService - SpringLifeCycleService start
2018-03-21 00:40:24.887 [restartedMain] INFO c.c.spring.SpringLifeCycle - SpringLifeCycle start
2018-03-21 00:40:25.062 [restartedMain] INFO o.s.b.d.a.OptionalLiveReloadServer - LiveReload server is running on port 35729
2018-03-21 00:40:25.122 [restartedMain] INFO o.s.j.e.a.AnnotationMBeanExporter - Registering beans for JMX exposure on startup
2018-03-21 00:40:25.140 [restartedMain] INFO com.crossoverjie.Application - Started Application in 2.309 seconds (JVM running for 3.681)
2018-03-21 00:40:25.143 [restartedMain] INFO com.crossoverjie.Application - start ok!
2018-03-21 00:40:25.153 [Thread-8] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3913adad: startup date [Wed Mar 21 00:40:23 CST 2018]; root of context hierarchy
2018-03-21 00:40:25.155 [Thread-8] INFO o.s.j.e.a.AnnotationMBeanExporter - Unregistering JMX-exposed beans on shutdown
2018-03-21 00:40:25.156 [Thread-8] INFO c.c.spring.SpringLifeCycle - SpringLifeCycle destroy
2018-03-21 00:40:25.156 [Thread-8] INFO c.c.s.service.SpringLifeCycleService - SpringLifeCycleService destroy
2018-03-21 00:40:25.156 [Thread-8] INFO c.c.spring.annotation.AnnotationBean - AnnotationBean destroy
```
直到 Spring 上下文销毁时则会调用自定义的销毁方法以及实现了 `DisposableBean` 的 `destroy()` 方法。
================================================
FILE: MD/third-party-component/cicada.md
================================================
<div align="center">
<img src="https://ws3.sinaimg.cn/large/006tNbRwly1fuvfxbc7y1j30go0e9aay.jpg" width="300"/>
<br/>
[](https://travis-ci.org/crossoverJie/cicada)
[](https://maven-badges.herokuapp.com/maven-central/top.crossoverjie.opensource/cicada-core/)
[](https://jq.qq.com/?_wv=1027&k=5HPYvQk)
[qq0groupsvg]: https://img.shields.io/badge/QQ%E7%BE%A4-787381170-yellowgreen.svg
[qq0group]: https://jq.qq.com/?_wv=1027&k=5HPYvQk
📘[特性](#features) |🌁[快速启动](#quick-start) | 🏖[性能测试](#performance-test) | 🌈[更新记录](#changelog) | 💡 [联系作者](#contact-author)|🇦🇺[English](https://github.com/TogetherOS/cicada)
</div><br>
## 简介
基于 Netty4 实现的快速、轻量级 WEB 框架;没有过多的依赖,核心 jar 包仅 `30KB`。
如果你感兴趣,请点 [Star](https://github.com/crossoverJie/cicada/stargazers)。
## 特性
- [x] 代码简洁,没有过多依赖。
- [x] 一行代码即可启动 HTTP 服务。
- [x] 自定义拦截器。
- [x] 灵活的传参方式。
- [x] `json` 响应格式。
- [x] 自定义配置。
- [x] 多种响应方式。
- [ ] `Cookie` 支持。
- [ ] 文件上传。
## 快速启动
创建一个 maven 项目,引入核心依赖。
```java
<dependency>
<groupId>top.crossoverjie.opensource</groupId>
<artifactId>cicada-core</artifactId>
<version>1.0.3</version>
</dependency>
```
启动类:
```java
public class MainStart {
public static void main(String[] args) throws InterruptedException {
CicadaServer.start(MainStart.class,"/cicada-example") ;
}
}
```
### 配置业务 Action
创建业务 Action 实现 `top.crossoverjie.cicada.server.action.WorkAction` 接口。
```java
@CicadaAction(value = "demoAction")
public class DemoAction implements WorkAction {
private static final Logger LOGGER = LoggerBuilder.getLogger(DemoAction.class) ;
private static AtomicLong index = new AtomicLong() ;
@Override
public void execute(CicadaContext context,Param paramMap) throws Exception {
String name = paramMap.getString("name");
Integer id = paramMap.getInteger("id");
LOGGER.info("name=[{}],id=[{}]" , name,id);
DemoResVO demoResVO = new DemoResVO() ;
demoResVO.setIndex(index.incrementAndGet());
WorkRes<DemoResVO> res = new WorkRes();
res.setCode(StatusEnum.SUCCESS.getCode());
res.setMessage(StatusEnum.SUCCESS.getMessage());
res.setDataBody(demoResVO) ;
context.json(res);
}
}
```
启动应用访问 [http://127.0.0.1:7317/cicada-example/demoAction?name=12345&id=10](http://127.0.0.1:7317/cicada-example/demoAction?name=12345&id=10)
```json
{
"code": "9000",
"dataBody": {
"index": 1
},
"message": "成功"
}
```
## Cicada 上下文
通过 `context.json(),context.text()` 方法可以选择不同的响应方式。
```java
@CicadaAction("textAction")
public class TextAction implements WorkAction {
@Override
public void execute(CicadaContext context, Param param) throws Exception {
String url = context.request().getUrl();
String method = context.request().getMethod();
context.text("hello world url=" + url + " method=" + method);
}
}
```

同时也可以根据 `context.request()` 获得请求上下文中的其他信息。

## 自定义配置
`cicada` 默认会读取 classpath 下的 `application.properties` 配置文件。
同时也可以自定义配置文件。
只需要继承 `top.crossoverjie.cicada.server.configuration.AbstractCicadaConfiguration`
并传入配置文件名称即可。比如:
```java
public class RedisConfiguration extends AbstractCicadaConfiguration {
public RedisConfiguration() {
super.setPropertiesName("redis.properties");
}
}
public class KafkaConfiguration extends AbstractCicadaConfiguration {
public KafkaConfiguration() {
super.setPropertiesName("kafka.properties");
}
}
```

### 获取配置
按照如下方式即可获取自定义配置:
```java
KafkaConfiguration configuration = (KafkaConfiguration) getConfiguration(KafkaConfiguration.class);
RedisConfiguration redisConfiguration = (RedisConfiguration) ConfigurationHolder.getConfiguration(RedisConfiguration.class);
ApplicationConfiguration applicationConfiguration = (ApplicationConfiguration) ConfigurationHolder.getConfiguration(ApplicationConfiguration.class);
String brokerList = configuration.get("kafka.broker.list");
String redisHost = redisConfiguration.get("redis.host");
String port = applicationConfiguration.get("cicada.port");
LOGGER.info("Configuration brokerList=[{}],redisHost=[{}] port=[{}]",brokerList,redisHost,port);
```
### 外置配置文件
当然在特殊环境中(`dev/test/pro`)也可以读取外置配置文件。只需要加上启动参数,保证参数名称和文件名一致即可。
```shell
-Dapplication.properties=/xx/application.properties
-Dkafka.properties=/xx/kakfa.properties
-Dredis.properties=/xx/redis.properties
```
## 自定义拦截器
实现 `top.crossoverjie.cicada.example.intercept.CicadaInterceptor` 接口。
```java
@Interceptor(value = "executeTimeInterceptor")
public class ExecuteTimeInterceptor implements CicadaInterceptor {
private static final Logger LOGGER = LoggerBuilder.getLogger(ExecuteTimeInterceptor.class);
private Long start;
private Long end;
@Override
public void before(Param param) {
start = System.currentTimeMillis();
}
@Override
public void after(Param param) {
end = System.currentTimeMillis();
LOGGER.info("cast [{}] times", end - start);
}
}
```
### 拦截适配器
同样也可以只实现其中一个方法,只需要继承 `top.crossoverjie.cicada.server.intercept.AbstractCicadaInterceptorAdapter` 抽象类。
```java
@Interceptor(value = "loggerInterceptor")
public class LoggerInterceptorAbstract extends AbstractCicadaInterceptorAdapter {
private static final Logger LOGGER = LoggerBuilder.getLogger(LoggerInterceptorAbstract.class) ;
@Override
public void before(Param param) {
LOGGER.info("logger param=[{}]",param.toString());
}
}
```
## 性能测试

> 测试条件:100 threads and 100 connections ;1G RAM/4 CPU。
**每秒将近 10W 请求。**
## 更新记录
### v1.0.3
- 修复 [#9](https://github.com/TogetherOS/cicada/issues/9)
- 修复 [#8](https://github.com/TogetherOS/cicada/issues/8),多种响应方式。
- 重构了核心代码,新增上下文环境。
- 优雅停机。
### v1.0.2
- 修复 [#6](https://github.com/TogetherOS/cicada/issues/6)
- 自定义配置文件。
- 灵活使用配置。
- 重构代码。
## 联系作者
> crossoverJie#gmail.com
<img src="https://ws2.sinaimg.cn/large/006tKfTcly1fsa01u7ro1j30gs0howfq.jpg" width="300"/>
## 特别感谢
- [Netty](https://github.com/netty/netty)
- [blade](https://github.com/lets-blade/blade)
================================================
FILE: MD/third-party-component/guava-cache.md
================================================

## 前言
Google 出的 [Guava](https://github.com/google/guava) 是 Java 核心增强的库,应用非常广泛。
我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的。
## 缓存
> 本次主要讨论缓存。
缓存在日常开发中举足轻重,如果你的应用对某类数据有着较高的读取频次,并且改动较小时那就非常适合利用缓存来提高性能。
缓存之所以可以提高性能是因为它的读取效率很高,就像是 CPU 的 `L1、L2、L3` 缓存一样,级别越高相应的读取速度也会越快。
但也不是什么好处都占,读取速度快了但是它的内存更小资源更宝贵,所以我们应当缓存真正需要的数据。
> 其实也就是典型的空间换时间。
下面谈谈 Java 中所用到的缓存。
<!--more-->
### JVM 缓存
首先是 JVM 缓存,也可以认为是堆缓存。
其实就是创建一些全局变量,如 `Map、List` 之类的容器用于存放数据。
这样的优势是使用简单但是也有以下问题:
- 只能显式的写入,清除数据。
- 不能按照一定的规则淘汰数据,如 `LRU,LFU,FIFO` 等。
- 清除数据时的回调通知。
- 其他一些定制功能等。
### Ehcache、Guava Cache
所以出现了一些专门用作 JVM 缓存的开源工具出现了,如本文提到的 Guava Cache。
它具有上文 JVM 缓存不具有的功能,如自动清除数据、多种清除算法、清除回调等。
但也正因为有了这些功能,这样的缓存必然会多出许多东西需要额外维护,自然也就增加了系统的消耗。
### 分布式缓存
刚才提到的两种缓存其实都是堆内缓存,只能在单个节点中使用,这样在分布式场景下就招架不住了。
于是也有了一些缓存中间件,如 Redis、Memcached,在分布式环境下可以共享内存。
具体不在本次的讨论范围。
## Guava Cache 示例
之所以想到 Guava 的 Cache,也是最近在做一个需求,大体如下:
> 从 Kafka 实时读取出应用系统的日志信息,该日志信息包含了应用的健康状况。
> 如果在时间窗口 N 内发生了 X 次异常信息,相应的我就需要作出反馈(报警、记录日志等)。
对此 Guava 的 Cache 就非常适合,我利用了它的 N 个时间内不写入数据时缓存就清空的特点,在每次读取数据时判断异常信息是否大于 X 即可。
伪代码如下:
```java
@Value("${alert.in.time:2}")
private int time ;
@Bean
public LoadingCache buildCache(){
return CacheBuilder.newBuilder()
.expireAfterWrite(time, TimeUnit.MINUTES)
.build(new CacheLoader<Long, AtomicLong>() {
@Override
public AtomicLong load(Long key) throws Exception {
return new AtomicLong(0);
}
});
}
/**
* 判断是否需要报警
*/
public void checkAlert() {
try {
if (counter.get(KEY).incrementAndGet() >= limit) {
LOGGER.info("***********报警***********");
//将缓存清空
counter.get(KEY).getAndSet(0L);
}
} catch (ExecutionException e) {
LOGGER.error("Exception", e);
}
}
```
首先是构建了 LoadingCache 对象,在 N 分钟内不写入数据时就回收缓存(当通过 Key 获取不到缓存时,默认返回 0)。
然后在每次消费时候调用 `checkAlert()` 方法进行校验,这样就可以达到上文的需求。
我们来设想下 Guava 它是如何实现过期自动清除数据,并且是可以按照 LRU 这样的方式清除的。
大胆假设下:
> 内部通过一个队列来维护缓存的顺序,每次访问过的数据移动到队列头部,并且额外开启一个线程来判断数据是否过期,过期就删掉。有点类似于我之前写过的 [动手实现一个 LRU cache](https://crossoverjie.top/%2F2018%2F04%2F07%2Falgorithm%2FLRU-cache%2F)
胡适说过:大胆假设小心论证
下面来看看 Guava 到底是怎么实现。
### 原理分析
看原理最好不过是跟代码一步步走了:
示例代码在这里:
[https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/CacheLoaderTest.java](https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/CacheLoaderTest.java)

为了能看出 Guava 是怎么删除过期数据的在获取缓存之前休眠了 5 秒钟,达到了超时条件。

最终会发现在 `com.google.common.cache.LocalCache` 类的 2187 行比较关键。
再跟进去之前第 2182 行会发现先要判断 count 是否大于 0,这个 count 保存的是当前缓存的数量,并用 volatile 修饰保证了可见性。
> 更多关于 volatile 的相关信息可以查看 [你应该知道的 volatile 关键字](https://crossoverjie.top/%2F2018%2F03%2F09%2Fvolatile%2F)
接着往下跟到:

2761 行,根据方法名称可以看出是判断当前的 Entry 是否过期,该 entry 就是通过 key 查询到的。

这里就很明显的看出是根据根据构建时指定的过期方式来判断当前 key 是否过期了。

如果过期就往下走,尝试进行过期删除(需要加锁,后面会具体讨论)。

到了这里也很清晰了:
- 获取当前缓存的总数量
- 自减一(前面获取了锁,所以线程安全)
- 删除并将更新的总数赋值到 count。
其实大体上就是这个流程,Guava 并没有按照之前猜想的另起一个线程来维护过期数据。
应该是以下原因:
- 新起线程需要资源消耗。
- 维护过期数据还要获取额外的锁,增加了消耗。
而在查询时候顺带做了这些事情,但是如果该缓存迟迟没有访问也会存在数据不能被回收的情况,不过这对于一个高吞吐的应用来说也不是问题。
## 总结
最后再来总结下 Guava 的 Cache。
其实在上文跟代码时会发现通过一个 key 定位数据时有以下代码:

如果有看过 [ConcurrentHashMap 的原理](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ConcurrentHashMap.md) 应该会想到这其实非常类似。
其实 Guava Cache 为了满足并发场景的使用,核心的数据结构就是按照 ConcurrentHashMap 来的,这里也是一个 key 定位到一个具体位置的过程。
> 先找到 Segment,再找具体的位置,等于是做了两次 Hash 定位。
上文有一个假设是对的,它内部会维护两个队列 `accessQueue,writeQueue` 用于记录缓存顺序,这样才可以按照顺序淘汰数据(类似于利用 LinkedHashMap 来做 LRU 缓存)。
同时从上文的构建方式来看,它也是[构建者模式](https://crossoverjie.top/2018/04/28/sbc/sbc7-Distributed-Limit/)来创建对象的。
因为作为一个给开发者使用的工具,需要有很多的自定义属性,利用构建则模式再合适不过了。
Guava 其实还有很多东西没谈到,比如它利用 GC 来回收内存,移除数据时的回调通知等。之后再接着讨论。
扫码关注微信公众号,第一时间获取消息。
## 进一步分析
## 前言
在上文「[Guava 源码分析(Cache 原理)](https://crossoverjie.top/2018/06/13/guava/guava-cache/)」中分析了 `Guava Cache` 的相关原理。
文末提到了**回收机制、移除时间通知**等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析。
> 在开始之前先补习下 Java 自带的两个特性,Guava 中都有具体的应用。
## Java 中的引用
首先是 Java 中的**引用**。
在之前分享过 JVM 是根据[可达性分析算法](https://github.com/crossoverJie/Java-Interview/blob/master/MD/GarbageCollection.md#%E5%8F%AF%E8%BE%BE%E6%80%A7%E5%88%86%E6%9E%90%E7%AE%97%E6%B3%95)找出需要回收的对象,判断对象的存活状态都和`引用`有关。
在 JDK1.2 之前这点设计的非常简单:一个对象的状态只有**引用**和**没被引用**两种区别。
<!--more-->
这样的划分对垃圾回收不是很友好,因为总有一些对象的状态处于这两之间。
因此 1.2 之后新增了四种状态用于更细粒度的划分引用关系:
- 强引用(Strong Reference):这种对象最为常见,比如 **`A a = new A();`**这就是典型的强引用;这样的强引用关系是不能被垃圾回收的。
- 软引用(Soft Reference):这样的引用表明一些有用但不是必要的对象,在将发生垃圾回收之前是需要将这样的对象再次回收。
- 弱引用(Weak Reference):这是一种比软引用还弱的引用关系,也是存放非必须的对象。当垃圾回收时,无论当前内存是否足够,这样的对象都会被回收。
- 虚引用(Phantom Reference):这是一种最弱的引用关系,甚至没法通过引用来获取对象,它唯一的作用就是在被回收时可以获得通知。
## 事件回调
事件回调其实是一种常见的设计模式,比如之前讲过的 [Netty](https://crossoverjie.top/categories/Netty/) 就使用了这样的设计。
这里采用一个 demo,试下如下功能:
- Caller 向 Notifier 提问。
- 提问方式是异步,接着做其他事情。
- Notifier 收到问题执行计算然后回调 Caller 告知结果。
在 Java 中利用接口来实现回调,所以需要定义一个接口:
```java
public interface CallBackListener {
/**
* 回调通知函数
* @param msg
*/
void callBackNotify(String msg) ;
}
```
Caller 中调用 Notifier 执行提问,调用时将接口传递过去:
```java
public class Caller {
private final static Logger LOGGER = LoggerFactory.getLogger(Caller.class);
private CallBackListener callBackListener ;
private Notifier notifier ;
private String question ;
/**
* 使用
*/
public void call(){
LOGGER.info("开始提问");
//新建线程,达到异步效果
new Thread(new Runnable() {
@Override
public void run() {
try {
notifier.execute(Caller.this,question);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
LOGGER.info("提问完毕,我去干其他事了");
}
//隐藏 getter/setter
}
```
Notifier 收到提问,执行计算(耗时操作),最后做出响应(回调接口,告诉 Caller 结果)。
```java
public class Notifier {
private final static Logger LOGGER = LoggerFactory.getLogger(Notifier.class);
public void execute(Caller caller, String msg) throws InterruptedException {
LOGGER.info("收到消息=【{}】", msg);
LOGGER.info("等待响应中。。。。。");
TimeUnit.SECONDS.sleep(2);
caller.getCallBackListener().callBackNotify("我在北京!");
}
}
```
模拟执行:
```java
public static void main(String[] args) {
Notifier notifier = new Notifier() ;
Caller caller = new Caller() ;
caller.setNotifier(notifier) ;
caller.setQuestion("你在哪儿!");
caller.setCallBackListener(new CallBackListener() {
@Override
public void callBackNotify(String msg) {
LOGGER.info("回复=【{}】" ,msg);
}
});
caller.call();
}
```
最后执行结果:
```log
2018-07-15 19:52:11.105 [main] INFO c.crossoverjie.guava.callback.Caller - 开始提问
2018-07-15 19:52:11.118 [main] INFO c.crossoverjie.guava.callback.Caller - 提问完毕,我去干其他事了
2018-07-15 19:52:11.117 [Thread-0] INFO c.c.guava.callback.Notifier - 收到消息=【你在哪儿!】
2018-07-15 19:52:11.121 [Thread-0] INFO c.c.guava.callback.Notifier - 等待响应中。。。。。
2018-07-15 19:52:13.124 [Thread-0] INFO com.crossoverjie.guava.callback.Main - 回复=【我在北京!】
```
这样一个模拟的异步事件回调就完成了。
## Guava 的用法
Guava 就是利用了上文的两个特性来实现了**引用回收**及**移除通知**。
### 引用
可以在初始化缓存时利用:
- CacheBuilder.weakKeys()
- CacheBuilder.weakValues()
- CacheBuilder.softValues()
来自定义键和值的引用关系。

在上文的分析中可以看出 Cache 中的 `ReferenceEntry` 是类似于 HashMap 的 Entry 存放数据的。
来看看 ReferenceEntry 的定义:
```java
interface ReferenceEntry<K, V> {
/**
* Returns the value reference from this entry.
*/
ValueReference<K, V> getValueReference();
/**
* Sets the value reference for this entry.
*/
void setValueReference(ValueReference<K, V> valueReference);
/**
* Returns the next entry in the chain.
*/
@Nullable
ReferenceEntry<K, V> getNext();
/**
* Returns the entry's hash.
*/
int getHash();
/**
* Returns the key for this entry.
*/
@Nullable
K getKey();
/*
* Used by entries that use access order. Access entries are maintained in a doubly-linked list.
* New entries are added at the tail of the list at write time; stale entries are expired from
* the head of the list.
*/
/**
* Returns the time that this entry was last accessed, in ns.
*/
long getAccessTime();
/**
* Sets the entry access time in ns.
*/
void setAccessTime(long time);
}
```
包含了很多常用的操作,如值引用、键引用、访问时间等。
根据 `ValueReference<K, V> getValueReference();` 的实现:

具有强引用和弱引用的不同实现。
key 也是相同的道理:

当使用这样的构造方式时,弱引用的 key 和 value 都会被垃圾回收。
当然我们也可以显式的回收:
```
/**
* Discards any cached value for key {@code key}.
* 单个回收
*/
void invalidate(Object key);
/**
* Discards any cached values for keys {@code keys}.
*
* @since 11.0
*/
void invalidateAll(Iterable<?> keys);
/**
* Discards all entries in the cache.
*/
void invalidateAll();
```
### 回调
改造了之前的例子:
```java
loadingCache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
LOGGER.info("删除原因={},删除 key={},删除 value={}",notification.getCause(),notification.getKey(),notification.getValue());
}
})
.build(new CacheLoader<Integer, AtomicLong>() {
@Override
public AtomicLong load(Integer key) throws Exception {
return new AtomicLong(0);
}
});
```
执行结果:
```log
2018-07-15 20:41:07.433 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:07.442 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 缓存的所有内容={1000=0}
2018-07-15 20:41:07.443 [main] INFO c.crossoverjie.guava.CacheLoaderTest - job running times=10
2018-07-15 20:41:10.461 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 删除原因=EXPIRED,删除 key=1000,删除 value=1
2018-07-15 20:41:10.462 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:10.462 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 缓存的所有内容={1000=0}
```
可以看出当缓存被删除的时候会回调我们自定义的函数,并告知删除原因。
那么 Guava 是如何实现的呢?

根据 LocalCache 中的 `getLiveValue()` 中判断缓存过期时,跟着这里的调用关系就会一直跟到:

`removeValueFromChain()` 中的:

`enqueueNotification()` 方法会将回收的缓存(包含了 key,value)以及回收原因包装成之前定义的事件接口加入到一个**本地队列**中。

这样一看也没有回调我们初始化时候的事件啊。
不过用过队列的同学应该能猜出,既然这里写入队列,那就肯定就有消费。
我们回到获取缓存的地方:

在 finally 中执行了 `postReadCleanup()` 方法;其实在这里面就是对刚才的队列进行了消费:

一直跟进来就会发现这里消费了队列,将之前包装好的移除消息调用了我们自定义的事件,这样就完成了一次事件回调。
## 总结
以上所有源码:
[https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/callback/Main.java](https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/callback/Main.java)
通过分析 Guava 的源码可以让我们学习到顶级的设计及实现方式,甚至自己也能尝试编写。
Guava 里还有很多强大的增强实现,值得我们再好好研究。
================================================
FILE: MD/third-party-component/seconds-kill.md
================================================

## 前言
之前在 [JCSprout](https://github.com/crossoverJie/JCSprout/blob/master/MD/Spike.md) 中提到过秒杀架构的设计,这次基于其中的理论简单实现了一下。
> 本次采用循序渐进的方式逐步提高性能达到并发秒杀的效果,文章较长请准备好瓜子板凳(liushuizhang😂)。
本文所有涉及的代码:
- [https://github.com/crossoverJie/SSM](https://github.com/crossoverJie/SSM)
- [https://github.com/crossoverJie/distributed-redis-tool](https://github.com/crossoverJie/distributed-redis-tool)
最终架构图:

<!--more-->
先简单根据这个图谈下请求的流转,因为后面不管怎么改进这个都是没有变的。
- 前端请求进入 `web` 层,对应的代码就是 `controller`。
- 之后将真正的库存校验、下单等请求发往 `Service` 层(其中 RPC 调用依然采用的 `dubbo`,只是更新为最新版本,本次不会过多讨论 dubbo 相关的细节,有兴趣的可以查看 [基于dubbo的分布式架构](https://crossoverjie.top/%2F2017%2F04%2F07%2FSSM11%2F))。
- `Service` 层再对数据进行落地,下单完成。
## 无限制
其实抛开秒杀这个场景来说正常的一个下单流程可以简单分为以下几步:
- 校验库存
- 扣库存
- 创建订单
- 支付
基于上文的架构所以我们有了以下实现:
先看看实际项目的结构:

还是和以前一样:
- 提供出一个 `API` 用于 `Service` 层实现,以及 `web` 层消费。
- web 层简单来说就是一个 `SpringMVC`。
- `Service` 层则是真正的数据落地。
- `SSM-SECONDS-KILL-ORDER-CONSUMER` 则是后文会提到的 `Kafka` 消费。
数据库也是只有简单的两张表模拟下单:
```sql
CREATE TABLE `stock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
`count` int(11) NOT NULL COMMENT '库存',
`sale` int(11) NOT NULL COMMENT '已售',
`version` int(11) NOT NULL COMMENT '乐观锁,版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
CREATE TABLE `stock_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sid` int(11) NOT NULL COMMENT '库存ID',
`name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8;
```
web 层 `controller` 实现:
```java
@Autowired
private StockService stockService;
@Autowired
private OrderService orderService;
@RequestMapping("/createWrongOrder/{sid}")
@ResponseBody
public String createWrongOrder(@PathVariable int sid) {
logger.info("sid=[{}]", sid);
int id = 0;
try {
id = orderService.createWrongOrder(sid);
} catch (Exception e) {
logger.error("Exception",e);
}
return String.valueOf(id);
}
```
其中 web 作为一个消费者调用看 `OrderService` 提供出来的 dubbo 服务。
Service 层,`OrderService` 实现:
首先是对 API 的实现(会在 API 提供出接口):
```java
@Service
public class OrderServiceImpl implements OrderService {
@Resource(name = "DBOrderService")
private com.crossoverJie.seconds.kill.service.OrderService orderService ;
@Override
public int createWrongOrder(int sid) throws Exception {
return orderService.createWrongOrder(sid);
}
}
```
这里只是简单调用了 `DBOrderService` 中的实现,DBOrderService 才是真正的数据落地,也就是写数据库了。
DBOrderService 实现:
```java
Transactional(rollbackFor = Exception.class)
@Service(value = "DBOrderService")
public class OrderServiceImpl implements OrderService {
@Resource(name = "DBStockService")
private com.crossoverJie.seconds.kill.service.StockService stockService;
@Autowired
private StockOrderMapper orderMapper;
@Override
public int createWrongOrder(int sid) throws Exception{
//校验库存
Stock stock = checkStock(sid);
//扣库存
saleStock(stock);
//创建订单
int id = createOrder(stock);
return id;
}
private Stock checkStock(int sid) {
Stock stock = stockService.getStockById(sid);
if (stock.getSale().equals(stock.getCount())) {
throw new RuntimeException("库存不足");
}
return stock;
}
private int saleStock(Stock stock) {
stock.setSale(stock.getSale() + 1);
return stockService.updateStockById(stock);
}
private int createOrder(Stock stock) {
StockOrder order = new StockOrder();
order.setSid(stock.getId());
order.setName(stock.getName());
int id = orderMapper.insertSelective(order);
return id;
}
}
```
> 预先初始化了 10 条库存。
手动调用下 `createWrongOrder/1` 接口发现:
库存表:

订单表:

一切看起来都没有问题,数据也正常。
但是当用 `JMeter` 并发测试时:

测试配置是:300个线程并发,测试两轮来看看数据库中的结果:



请求都响应成功,库存确实也扣完了,但是订单却生成了 **124** 条记录。
这显然是典型的超卖现象。
> 其实现在再去手动调用接口会返回库存不足,但为时晚矣。
## 乐观锁更新
怎么来避免上述的现象呢?
最简单的做法自然是乐观锁了,这里不过多讨论这个,不熟悉的朋友可以看下[这篇](http://crossoverjie.top/%2F2017%2F07%2F09%2FSSM15%2F)。
来看看具体实现:
> 其实其他的都没怎么改,主要是 Service 层。
```java
@Override
public int createOptimisticOrder(int sid) throws Exception {
//校验库存
Stock stock = checkStock(sid);
//乐观锁更新库存
saleStockOptimistic(stock);
//创建订单
int id = createOrder(stock);
return id;
}
private void saleStockOptimistic(Stock stock) {
int count = stockService.updateStockByOptimistic(stock);
if (count == 0){
throw new RuntimeException("并发更新库存失败") ;
}
}
```
对应的 XML:
```xml
<update id="updateByOptimistic" parameterType="com.crossoverJie.seconds.kill.pojo.Stock">
update stock
<set>
sale = sale + 1,
version = version + 1,
</set>
WHERE id = #{id,jdbcType=INTEGER}
AND version = #{version,jdbcType=INTEGER}
</update>
```
同样的测试条件,我们再进行上面的测试 `/createOptimisticOrder/1`:



这次发现无论是库存订单都是 OK 的。
查看日志发现:

很多并发请求会响应错误,这就达到了效果。
### 提高吞吐量
为了进一步提高秒杀时的吞吐量以及响应效率,这里的 web 和 Service 都进行了横向扩展。
- web 利用 Nginx 进行负载。
- Service 也是多台应用。


再用 JMeter 测试时可以直观的看到效果。
> 由于我是在阿里云的一台小水管服务器进行测试的,加上配置不高、应用都在同一台,所以并没有完全体现出性能上的优势( `Nginx` 做负载转发时候也会增加额外的网络消耗)。
### shell 脚本实现简单的 CI
由于应用多台部署之后,手动发版测试的痛苦相信经历过的都有体会。
这次并没有精力去搭建完整的 CI CD,只是写了一个简单的脚本实现了自动化部署,希望对这方面没有经验的同学带来一点启发:
#### 构建 web
```shell
#!/bin/bash
# 构建 web 消费者
#read appname
appname="consumer"
echo "input="$appname
PID=$(ps -ef | grep $appname | grep -v grep | awk '{print $2}')
# 遍历杀掉 pid
for var in ${PID[@]};
do
echo "loop pid= $var"
kill -9 $var
done
echo "kill $appname success"
cd ..
git pull
cd SSM-SECONDS-KILL
mvn -Dmaven.test.skip=true clean package
echo "build war success"
cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-WEB/target/SSM-SECONDS-KILL-WEB-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-consumer-8083/webapps
echo "cp tomcat-dubbo-consumer-8083/webapps ok!"
cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-WEB/target/SSM-SECONDS-KILL-WEB-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-consumer-7083-slave/webapps
echo "cp tomcat-dubbo-consumer-7083-slave/webapps ok!"
sh /home/crossoverJie/tomcat/tomcat-dubbo-consumer-8083/bin/startup.sh
echo "tomcat-dubbo-consumer-8083/bin/startup.sh success"
sh /home/crossoverJie/tomcat/tomcat-dubbo-consumer-7083-slave/bin/startup.sh
echo "tomcat-dubbo-consumer-7083-slave/bin/startup.sh success"
echo "start $appname success"
```
#### 构建 Service
```shell
# 构建服务提供者
#read appname
appname="provider"
echo "input="$appname
PID=$(ps -ef | grep $appname | grep -v grep | awk '{print $2}')
#if [ $? -eq 0 ]; then
# echo "process id:$PID"
#else
# echo "process $appname not exit"
# exit
#fi
# 遍历杀掉 pid
for var in ${PID[@]};
do
echo "loop pid= $var"
kill -9 $var
done
echo "kill $appname success"
cd ..
git pull
cd SSM-SECONDS-KILL
mvn -Dmaven.test.skip=true clean package
echo "build war success"
cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-SERVICE/target/SSM-SECONDS-KILL-SERVICE-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-provider-8080/webapps
echo "cp tomcat-dubbo-provider-8080/webapps ok!"
cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-SERVICE/target/SSM-SECONDS-KILL-SERVICE-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-provider-7080-slave/webapps
echo "cp tomcat-dubbo-provider-7080-slave/webapps ok!"
sh /home/crossoverJie/tomcat/tomcat-dubbo-provider-8080/bin/startup.sh
echo "tomcat-dubbo-provider-8080/bin/startup.sh success"
sh /home/crossoverJie/tomcat/tomcat-dubbo-provider-7080-slave/bin/startup.sh
echo "tomcat-dubbo-provider-8080/bin/startup.sh success"
echo "start $appname success"
```
之后每当我有更新,只需要执行这两个脚本就可以帮我自动构建。
都是最基础的 Linux 命令,相信大家都看得明白。
## 乐观锁更新 + 分布式限流
上文的结果看似没有问题,其实还差得远呢。
这里只是模拟了 300 个并发没有问题,但是当请求达到了 3000 ,3W,300W 呢?
虽说可以横向扩展可以支撑更多的请求。
但是能不能利用最少的资源解决问题呢?
其实仔细分析下会发现:
> 假设我的商品一共只有 10 个库存,那么无论你多少人来买其实最终也最多只有 10 人可以下单成功。
所以其中会有 `99%` 的请求都是无效的。
大家都知道:大多数应用数据库都是压倒骆驼的最后一根稻草。
通过 `Druid` 的监控来看看之前请求数据库的情况:
因为 Service 是两个应用。



数据库也有 20 多个连接。
怎么样来优化呢?
其实很容易想到的就是[分布式限流](http://crossoverjie.top/2018/04/28/sbc/sbc7-Distributed-Limit/)。
我们将并发控制在一个可控的范围之内,然后快速失败这样就能最大程度的保护系统。
### distributed-redis-tool ⬆️v1.0.3
为此还对 [https://github.com/crossoverJie/distributed-redis-tool](https://github.com/crossoverJie/distributed-redis-tool) 进行了小小的升级。
因为加上该组件之后所有的请求都会经过 Redis,所以对 Redis 资源的使用也是要非常小心。
#### API 更新
修改之后的 API 如下:
```java
@Configuration
public class RedisLimitConfig {
private Logger logger = LoggerFactory.getLogger(RedisLimitConfig.class);
@Value("${redis.limit}")
private int limit;
@Autowired
private JedisConnectionFactory jedisConnectionFactory;
@Bean
public RedisLimit build() {
RedisLimit redisLimit = new RedisLimit.Builder(jedisConnectionFactory, RedisToolsConstant.SINGLE)
.limit(limit)
.build();
return redisLimit;
}
}
```
这里构建器改用了 `JedisConnectionFactory`,所以得配合 Spring 来一起使用。
并在初始化时显示传入 Redis 是以集群方式部署还是单机(强烈建议集群,限流之后对 Redis 还是有一定的压力)。
##### 限流实现
既然 API 更新了,实现自然也要修改:
```java
/**
* limit traffic
* @return if true
*/
public boolean limit() {
//get connection
Object connection = getConnection();
Object result = limitRequest(connection);
if (FAIL_CODE != (Long) result) {
return true;
} else {
return false;
}
}
private Object limitRequest(Object connection) {
Object result = null;
String key = String.valueOf(System.currentTimeMillis() / 1000);
if (connection instanceof Jedis){
result = ((Jedis)connection).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));
((Jedis) connection).close();
}else {
result = ((JedisCluster) connection).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));
try {
((JedisCluster) connection).close();
} catch (IOException e) {
logger.error("IOException",e);
}
}
return result;
}
private Object getConnection() {
Object connection ;
if (type == RedisToolsConstant.SINGLE){
RedisConnection redisConnection = jedisConnectionFactory.getConnection();
connection = redisConnection.getNativeConnection();
}else {
RedisClusterConnection clusterConnection = jedisConnectionFactory.getClusterConnection();
connection = clusterConnection.getNativeConnection() ;
}
return connection;
}
```
如果是原生的 Spring 应用得采用 `@SpringControllerLimit(errorCode = 200)` 注解。
实际使用如下:
web 端:
```java
/**
* 乐观锁更新库存 限流
* @param sid
* @return
*/
@SpringControllerLimit(errorCode = 200)
@RequestMapping("/createOptimisticLimitOrder/{sid}")
@ResponseBody
public String createOptimisticLimitOrder(@PathVariable int sid) {
logger.info("sid=[{}]", sid);
int id = 0;
try {
id = orderService.createOptimisticOrder(sid);
} catch (Exception e) {
logger.error("Exception",e);
}
return String.valueOf(id);
}
```
Service 端就没什么更新了,依然是采用的乐观锁更新数据库。
再压测看下效果 `/createOptimisticLimitOrderByRedis/1`:





首先是看结果没有问题,再看数据库连接以及并发请求数都有**明显的下降**。
## 乐观锁更新 + 分布式限流 + Redis 缓存
其实仔细观察 Druid 监控数据发现这个 SQL 被多次查询:

其实这是实时查询库存的 SQL,主要是为了在每次下单之前判断是否还有库存。
**这也是个优化点**。
这种数据我们完全可以放在内存中,效率比在数据库要高很多。
由于我们的应用是分布式的,所以堆内缓存显然不合适,Redis 就非常适合。
这次主要改造的是 Service 层:
- 每次查询库存时走 Redis。
- 扣库存时更新 Redis。
- 需要提前将库存信息写入 Redis(手动或者程序自动都可以)。
主要代码如下:
```java
@Override
public int createOptimisticOrderUseRedis(int sid) throws Exception {
//检验库存,从 Redis 获取
Stock stock = checkStockByRedis(sid);
//乐观锁更新库存 以及更新 Redis
saleStockOptimisticByRedis(stock);
//创建订单
int id = createOrder(stock);
return id ;
}
private Stock checkStockByRedis(int sid) throws Exception {
Integer count = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_COUNT + sid));
Integer sale = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_SALE + sid));
if (count.equals(sale)){
throw new RuntimeException("库存不足 Redis currentCount=" + sale);
}
Integer version = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_VERSION + sid));
Stock stock = new Stock() ;
stock.setId(sid);
stock.setCount(count);
stock.setSale(sale);
stock.setVersion(version);
return stock;
}
/**
* 乐观锁更新数据库 还要更新 Redis
* @param stock
*/
private void saleStockOptimisticByRedis(Stock stock) {
int count = stockService.updateStockByOptimistic(stock);
if (count == 0){
throw new RuntimeException("并发更新库存失败") ;
}
//自增
redisTemplate.opsForValue().increment(RedisKeysConstant.STOCK_SALE + stock.getId(),1) ;
redisTemplate.opsForValue().increment(RedisKeysConstant.STOCK_VERSION + stock.getId(),1) ;
}
```
压测看看实际效果 `/createOptimisticLimitOrderByRedis/1`:




最后发现数据没问题,数据库的请求与并发也都下来了。
## 乐观锁更新 + 分布式限流 + Redis 缓存 + Kafka 异步
最后的优化还是想如何来再次提高吞吐量以及性能的。
我们上文所有例子其实都是同步请求,完全可以利用同步转异步来提高性能啊。
这里我们将写订单以及更新库存的操作进行异步化,利用 `Kafka` 来进行解耦和队列的作用。
每当一个请求通过了限流到达了 Service 层通过了库存校验之后就将订单信息发给 Kafka ,这样一个请求就可以直接返回了。
消费程序再对数据进行入库落地。
因为异步了,所以最终需要采取回调或者是其他提醒的方式提醒用户购买完成。
这里代码较多就不贴了,消费程序其实就是把之前的 Service 层的逻辑重写了一遍,不过采用的是 SpringBoot。
感兴趣的朋友可以看下。
[https://github.com/crossoverJie/SSM/tree/master/SSM-SECONDS-KILL/SSM-SECONDS-KILL-ORDER-CONSUMER](https://github.com/crossoverJie/SSM/tree/master/SSM-SECONDS-KILL/SSM-SECONDS-KILL-ORDER-CONSUMER)
## 总结
其实经过上面的一顿优化总结起来无非就是以下几点:
- 尽量将请求拦截在上游。
- 还可以根据 UID 进行限流。
- 最大程度的减少请求落到 DB。
- 多利用缓存。
- 同步操作异步化。
- fail fast,尽早失败,保护应用。
码字不易,这应该是我写过字数最多的了,想想当年高中 800 字的作文都憋不出来😂,可想而知是有多难得了。
**以上内容欢迎讨论**。
### 号外
最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。
> 地址: [https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
================================================
FILE: README.md
================================================
<div align="center">
<img src="https://s2.loli.net/2024/05/17/D8brxzCPh5vpOFk.png" width=""/>
<br/>
[](https://travis-ci.org/crossoverJie/JCSprout)
[](https://jq.qq.com/?_wv=1027&k=5HPYvQk)
[qq0groupsvg]: https://img.shields.io/badge/QQ%E7%BE%A4-787381170-yellowgreen.svg
[qq0group]: https://jq.qq.com/?_wv=1027&k=5HPYvQk
</div><br>
> `Java Core Sprout`:处于萌芽阶段的 Java 核心知识库。
**访问这里获取更好的阅读体验**:[https://crossoverjie.top/JCSprout/](https://crossoverjie.top/JCSprout/)
<br/>
<div align="center">
<a href="https://t.zsxq.com/odQDJ" target="_blank"><img src="https://s2.loli.net/2024/05/17/zRkabDu2SKfChLX.png" alt="202405171520366.png"></a>
</div>
最近开通了知识星球,感谢大家对 `JCSprout` 的支持,为大家提供 100 份 10 元优惠券,也就是 69-10=59 元,具体福利大家可以扫码参考再决定是否加入。
> PS: 后续会继续维护该项目,同时加入现在热门的 Golang/kubernetes/OpenTelemetry 等知识点,感兴趣的可以加入星球当面催更(当然内容也会更新到这个项目里)。
| 📊 |⚔️ | 🖥 | 🚏 | 🏖 | 🌁| 📮 | 🔍 | 🚀 | 🌈 |💡
| :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:|:------:|
| [集合](#常用集合) | [多线程](#java-多线程)|[JVM](#jvm) | [分布式](#分布式相关) |[框架](#常用框架第三方组件)|[架构设计](#架构设计)| [数据库](#db-相关) |[算法](#数据结构与算法)|[Netty](#netty-相关)| [附加技能](#附加技能)|[联系作者](#联系作者) |
### 常用集合
- [ArrayList/Vector](https://github.com/crossoverJie/JCSprout/blob/master/MD/ArrayList.md)
- [LinkedList](https://github.com/crossoverJie/JCSprout/blob/master/MD/LinkedList.md)
- [HashMap](https://github.com/crossoverJie/JCSprout/blob/master/MD/HashMap.md)
- [HashSet](https://github.com/crossoverJie/JCSprout/blob/master/MD/collection/HashSet.md)
- [LinkedHashMap](https://github.com/crossoverJie/JCSprout/blob/master/MD/collection/LinkedHashMap.md)
### Java 多线程
- [多线程中的常见问题](https://github.com/crossoverJie/JCSprout/blob/master/MD/Thread-common-problem.md)
- [synchronized 关键字原理](https://github.com/crossoverJie/JCSprout/blob/master/MD/Synchronize.md)
- [多线程的三大核心](https://github.com/crossoverJie/JCSprout/blob/master/MD/Threadcore.md)
- [对锁的一些认知](https://github.com/crossoverJie/JCSprout/blob/master/MD/Java-lock.md)
- [ReentrantLock 实现原理 ](https://github.com/crossoverJie/JCSprout/blob/master/MD/ReentrantLock.md)
- [ConcurrentHashMap 的实现原理](https://github.com/crossoverJie/JCSprout/blob/master/MD/ConcurrentHashMap.md)
- [如何优雅的使用和理解线程池](https://github.com/crossoverJie/JCSprout/blob/master/MD/ThreadPoolExecutor.md)
- [深入理解线程通信](https://github.com/crossoverJie/JCSprout/blob/master/MD/concurrent/thread-communication.md)
- [一个线程罢工的诡异事件](docs/thread/thread-gone.md)
- [线程池中你不容错过的一些细节](docs/thread/thread-gone2.md)
- [『并发包入坑指北』之阻塞队列](docs/thread/ArrayBlockingQueue.md)
### JVM
- [Java 运行时内存划分](https://github.com/crossoverJie/JCSprout/blob/master/MD/MemoryAllocation.md)
- [类加载机制](https://github.com/crossoverJie/JCSprout/blob/master/MD/ClassLoad.md)
- [OOM 分析](https://github.com/crossoverJie/JCSprout/blob/master/MD/OOM-analysis.md)
- [垃圾回收](https://github.com/crossoverJie/JCSprout/blob/master/MD/GarbageCollection.md)
- [对象的创建与内存分配](https://github.com/crossoverJie/JCSprout/blob/master/MD/newObject.md)
- [你应该知道的 volatile 关键字](https://github.com/crossoverJie/JCSprout/blob/master/MD/concurrent/volatile.md)
- [一次内存溢出排查优化实战](https://crossoverjie.top/2018/08/29/java-senior/OOM-Disruptor/)
- [一次 HashSet 所引起的并发问题](docs/jvm/JVM-concurrent-HashSet-problem.md)
- [一次生产 CPU 100% 排查优化实践](docs/jvm/cpu-percent-100.md)
### 分布式相关
- [分布式限流](http://crossoverjie.top/2018/04/28/sbc/sbc7-Distributed-Limit/)
- [基于 Redis 的分布式锁](http://crossoverjie.top/2018/03/29/distributed-lock/distributed-lock-redis/)
- [分布式缓存设计](https://github.com/crossoverJie/JCSprout/blob/master/MD/Cache-design.md)
- [分布式 ID 生成器](https://github.com/crossoverJie/JCSprout/blob/master/MD/ID-generator.md)
### 常用框架\第三方组件
- [Spring Bean 生命周期](https://github.com/crossoverJie/JCSprout/blob/master/MD/spring/spring-bean-lifecycle.md)
- [Spring AOP 的实现原理](https://github.com/crossoverJie/JCSprout/blob/master/MD/SpringAOP.md)
- [Guava 源码分析(Cache 原理)](https://crossoverjie.top/2018/06/13/guava/guava-cache/)
- [轻量级 HTTP 框架](https://github.com/crossoverJie/cicada)
- [Kafka produce 源码分析](https://github.com/crossoverJie/JCSprout/blob/master/MD/kafka/kafka-product.md)
- [Kafka 消费实践](https://github.com/crossoverJie/JCSprout/blob/master/docs/frame/kafka-consumer.md)
### 架构设计
- [秒杀系统设计](https://github.com/crossoverJie/JCSprout/blob/master/MD/Spike.md)
- [秒杀架构实践](http://crossoverjie.top/2018/05/07/ssm/SSM18-seconds-kill/)
- [设计一个百万级的消息推送系统](https://github.com/crossoverJie/JCSprout/blob/master/MD/architecture-design/million-sms-push.md)
### DB 相关
- [MySQL 索引原理](https://github.com/crossoverJie/JCSprout/blob/master/MD/MySQL-Index.md)
- [SQL 优化](https://github.com/crossoverJie/JCSprout/blob/master/MD/SQL-optimization.md)
- [数据库水平垂直拆分](https://github.com/crossoverJie/JCSprout/blob/master/MD/DB-split.md)
- [一次分表踩坑实践的探讨](docs/db/sharding-db.md)
### 数据结构与算法
- [红包算法](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/red/RedPacket.java)
- [二叉树层序遍历](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/algorithm/BinaryNode.java#L76-L101)
- [是否为快乐数字](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/algorithm/HappyNum.java#L38-L55)
- [链表是否有环](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/algorithm/LinkLoop.java#L32-L59)
- [从一个数组中返回两个值相加等于目标值的下标](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/algorithm/TwoSum.java#L38-L59)
- [一致性 Hash 算法原理](https://github.com/crossoverJie/JCSprout/blob/master/MD/Consistent-Hash.md)
- [一致性 Hash 算法实践](https://github.com/crossoverJie/JCSprout/blob/master/docs/algorithm/consistent-hash-implement.md)
- [限流算法](https://github.com/crossoverJie/JCSprout/blob/master/MD/Limiting.md)
- [三种方式反向打印单向链表](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/algorithm/ReverseNode.java)
- [合并两个排好序的链表](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/algorithm/MergeTwoSortedLists.java)
- [两个栈实现队列](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/algorithm/TwoStackQueue.java)
- [动手实现一个 LRU cache](http://crossoverjie.top/2018/04/07/algorithm/LRU-cache/)
- [链表排序](./src/main/java/com/crossoverjie/algorithm/LinkedListMergeSort.java)
- [数组右移 k 次](./src/main/java/com/crossoverjie/algorithm/ArrayKShift.java)
- [交替打印奇偶数](https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/actual/TwoThread.java)
- [亿级数据中判断数据是否不存在](https://github.com/crossoverJie/JC
gitextract_c7550992/
├── .github/
│ ├── ISSUE_TEMPLATE
│ └── PULL_REQUEST_TEMPLATE
├── .travis.yml
├── 79884.log
├── LICENSE
├── MD/
│ ├── ArrayList.md
│ ├── Cache-design.md
│ ├── ClassLoad.md
│ ├── ConcurrentHashMap.md
│ ├── Consistent-Hash.md
│ ├── DB-split.md
│ ├── GarbageCollection.md
│ ├── HashMap.md
│ ├── ID-generator.md
│ ├── Java-lock.md
│ ├── Limiting.md
│ ├── LinkedList.md
│ ├── MemoryAllocation.md
│ ├── MySQL-Index.md
│ ├── OOM-analysis.md
│ ├── ReentrantLock.md
│ ├── SQL-optimization.md
│ ├── Spike.md
│ ├── SpringAOP.md
│ ├── Synchronize.md
│ ├── TCP-IP.md
│ ├── Thread-common-problem.md
│ ├── ThreadPoolExecutor.md
│ ├── Threadcore.md
│ ├── additional-skills/
│ │ └── how-to-use-git-efficiently.md
│ ├── architecture-design/
│ │ └── million-sms-push.md
│ ├── collection/
│ │ ├── HashSet.md
│ │ └── LinkedHashMap.md
│ ├── concurrent/
│ │ ├── thread-communication.md
│ │ └── volatile.md
│ ├── distributed/
│ │ ├── Distributed-Limit.md
│ │ └── distributed-lock-redis.md
│ ├── jvm/
│ │ └── OOM-Disruptor.md
│ ├── kafka/
│ │ └── kafka-product.md
│ ├── newObject.md
│ ├── soft-skills/
│ │ ├── Interview-experience.md
│ │ └── how-to-be-developer.md
│ ├── spring/
│ │ └── spring-bean-lifecycle.md
│ └── third-party-component/
│ ├── cicada.md
│ ├── guava-cache.md
│ └── seconds-kill.md
├── README.md
├── docs/
│ ├── .nojekyll
│ ├── README.md
│ ├── _coverpage.md
│ ├── _sidebar.md
│ ├── algorithm/
│ │ ├── Consistent-Hash.md
│ │ ├── LRU-cache.md
│ │ ├── Limiting.md
│ │ ├── common-algorithm.md
│ │ ├── consistent-hash-implement.md
│ │ └── guava-bloom-filter.md
│ ├── architecture-design/
│ │ ├── Spike.md
│ │ ├── million-sms-push.md
│ │ └── seconds-kill.md
│ ├── collections/
│ │ ├── ArrayList.md
│ │ ├── HashMap.md
│ │ ├── HashSet.md
│ │ ├── LinkedHashMap.md
│ │ └── LinkedList.md
│ ├── contactme.md
│ ├── db/
│ │ ├── DB-split.md
│ │ ├── MySQL-Index.md
│ │ ├── SQL-optimization.md
│ │ └── sharding-db.md
│ ├── distributed/
│ │ ├── Cache-design.md
│ │ ├── Distributed-Limit.md
│ │ ├── ID-generator.md
│ │ └── distributed-lock-redis.md
│ ├── frame/
│ │ ├── SpringAOP.md
│ │ ├── guava-cache.md
│ │ ├── kafka-consumer.md
│ │ ├── kafka-product.md
│ │ └── spring-bean-lifecycle.md
│ ├── index.html
│ ├── jvm/
│ │ ├── ClassLoad.md
│ │ ├── GarbageCollection.md
│ │ ├── JVM-concurrent-HashSet-problem.md
│ │ ├── MemoryAllocation.md
│ │ ├── OOM-Disruptor.md
│ │ ├── OOM-analysis.md
│ │ ├── cpu-percent-100.md
│ │ ├── newObject.md
│ │ └── volatile.md
│ ├── netty/
│ │ ├── Netty(1)TCP-Heartbeat.md
│ │ ├── Netty(2)Thread-model.md
│ │ ├── cicada.md
│ │ └── cim.md
│ ├── soft-skills/
│ │ ├── Interview-experience.md
│ │ ├── TCP-IP.md
│ │ ├── how-to-be-developer.md
│ │ └── how-to-use-git-efficiently.md
│ └── thread/
│ ├── ArrayBlockingQueue.md
│ ├── ConcurrentHashMap.md
│ ├── Java-lock.md
│ ├── ReentrantLock.md
│ ├── Synchronize.md
│ ├── Thread-common-problem.md
│ ├── ThreadPoolExecutor.md
│ ├── Threadcore.md
│ ├── thread-communication.md
│ ├── thread-gone.md
│ └── thread-gone2.md
├── java_pid26365.hprof
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── crossoverjie/
│ │ ├── Application.java
│ │ ├── actual/
│ │ │ ├── FourThreadPrinter.java
│ │ │ ├── LRUAbstractMap.java
│ │ │ ├── LRULinkedMap.java
│ │ │ ├── LRUMap.java
│ │ │ ├── NotifyAll.java
│ │ │ ├── ReadFile.java
│ │ │ ├── Search.java
│ │ │ ├── ThreadCommunication.java
│ │ │ ├── TwoThread.java
│ │ │ ├── TwoThreadNonBlocking.java
│ │ │ ├── TwoThreadWaitNotify.java
│ │ │ └── TwoThreadWaitNotifySimple.java
│ │ ├── algorithm/
│ │ │ ├── ArrayKShift.java
│ │ │ ├── BinaryNode.java
│ │ │ ├── BinaryNodeTravel.java
│ │ │ ├── BloomFilters.java
│ │ │ ├── HappyNum.java
│ │ │ ├── LinkLoop.java
│ │ │ ├── LinkedListMergeSort.java
│ │ │ ├── MergeTwoSortedLists.java
│ │ │ ├── ReverseNode.java
│ │ │ ├── TwoArray.java
│ │ │ ├── TwoStackQueue.java
│ │ │ └── TwoSum.java
│ │ ├── basic/
│ │ │ ├── CollectionsTest.java
│ │ │ ├── HashMapTest.java
│ │ │ └── StringTest.java
│ │ ├── classloader/
│ │ │ ├── ChildClass.java
│ │ │ ├── Main.java
│ │ │ └── SuperClass.java
│ │ ├── concurrent/
│ │ │ ├── ArrayQueue.java
│ │ │ ├── CustomThreadPool.java
│ │ │ ├── Singleton.java
│ │ │ ├── StopThread.java
│ │ │ ├── ThreadState.java
│ │ │ ├── Volatile.java
│ │ │ ├── VolatileInc.java
│ │ │ ├── communication/
│ │ │ │ ├── MultipleThreadCountDownKit.java
│ │ │ │ └── Notify.java
│ │ │ └── future/
│ │ │ ├── Callable.java
│ │ │ ├── Future.java
│ │ │ └── FutureTask.java
│ │ ├── design/
│ │ │ └── pattern/
│ │ │ ├── chainofresponsibility/
│ │ │ │ ├── Main.java
│ │ │ │ ├── MsgProcessChain.java
│ │ │ │ ├── Process.java
│ │ │ │ └── impl/
│ │ │ │ ├── CopyrightProcess.java
│ │ │ │ ├── SensitiveWordProcess.java
│ │ │ │ └── TypoProcess.java
│ │ │ └── factorymethod/
│ │ │ ├── Animal.java
│ │ │ ├── AnimalFactory.java
│ │ │ ├── Cat.java
│ │ │ ├── CatFactory.java
│ │ │ ├── Fish.java
│ │ │ ├── FishFactory.java
│ │ │ └── Main.java
│ │ ├── disruptor/
│ │ │ ├── LongEvent.java
│ │ │ ├── LongEventFactory.java
│ │ │ ├── LongEventHandler.java
│ │ │ ├── LongEventMain.java
│ │ │ └── LongEventProducer.java
│ │ ├── gc/
│ │ │ └── MinorGC.java
│ │ ├── guava/
│ │ │ ├── CacheLoaderTest.java
│ │ │ └── callback/
│ │ │ ├── CallBackListener.java
│ │ │ ├── Caller.java
│ │ │ ├── Main.java
│ │ │ └── Notifier.java
│ │ ├── hystrix/
│ │ │ ├── CommandOrder.java
│ │ │ ├── CommandTest.java
│ │ │ └── CommandUser.java
│ │ ├── oom/
│ │ │ └── heap/
│ │ │ ├── HeapOOM.java
│ │ │ └── MetaSpaceOOM.java
│ │ ├── proxy/
│ │ │ ├── cglib/
│ │ │ │ ├── RealSubject.java
│ │ │ │ └── RealSubjectIntercept.java
│ │ │ └── jdk/
│ │ │ ├── CustomizeHandle.java
│ │ │ ├── ISubject.java
│ │ │ └── impl/
│ │ │ └── ISubjectImpl.java
│ │ ├── red/
│ │ │ └── RedPacket.java
│ │ ├── spring/
│ │ │ ├── LifeCycleConfig.java
│ │ │ ├── SpringLifeCycle.java
│ │ │ ├── annotation/
│ │ │ │ └── AnnotationBean.java
│ │ │ ├── aware/
│ │ │ │ └── SpringLifeCycleAware.java
│ │ │ ├── processor/
│ │ │ │ └── SpringLifeCycleProcessor.java
│ │ │ └── service/
│ │ │ └── SpringLifeCycleService.java
│ │ ├── synchronize/
│ │ │ └── Synchronize.java
│ │ └── thread/
│ │ └── ThreadExceptionTest.java
│ └── resources/
│ ├── application.properties
│ └── logback.xml
└── test/
└── java/
└── com/
└── crossoverjie/
├── actual/
│ ├── AbstractMapTest.java
│ ├── LRULinkedMapTest.java
│ └── LRUMapTest.java
├── algorithm/
│ ├── BinaryNodeTest.java
│ ├── BinaryNodeTravelTest.java
│ ├── BloomFiltersTest.java
│ ├── HappyNumTest.java
│ ├── LinkLoopTest.java
│ ├── LinkedListMergeSortTest.java
│ ├── MergeTwoSortedListsTest.java
│ ├── ReverseNodeTest.java
│ ├── TwoStackQueueTest.java
│ └── TwoSumTest.java
├── concurrent/
│ ├── ArrayQueueTest.java
│ ├── CustomThreadPoolExeceptionTest.java
│ ├── CustomThreadPoolFutureTest.java
│ ├── CustomThreadPoolTest.java
│ ├── MultipleThreadCountDownKitTest.java
│ └── ThreadPoolTest.java
├── kafka/
│ └── KafkaTest.java
├── proxy/
│ └── JDKProxyTest.java
├── red/
│ └── RedPacketTest.java
└── reference/
└── ReferenceTest.java
SYMBOL INDEX (486 symbols across 109 files)
FILE: src/main/java/com/crossoverjie/Application.java
class Application (line 12) | @SpringBootApplication
method main (line 18) | public static void main(String[] args) throws Exception {
FILE: src/main/java/com/crossoverjie/actual/FourThreadPrinter.java
class FourThreadPrinter (line 16) | public class FourThreadPrinter extends Thread {
method FourThreadPrinter (line 41) | public FourThreadPrinter(String name, int currentType) {
method run (line 47) | @Override
method updateCondition (line 63) | private void updateCondition() {
method main (line 77) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/actual/LRUAbstractMap.java
class LRUAbstractMap (line 27) | public class LRUAbstractMap extends java.util.AbstractMap {
method LRUAbstractMap (line 77) | public LRUAbstractMap() {
method executeCheckTime (line 90) | private void executeCheckTime() {
method entrySet (line 101) | @Override
method put (line 106) | @Override
method get (line 153) | @Override
method remove (line 191) | @Override
method sizeUp (line 235) | private void sizeUp(){
method sizeDown (line 262) | private void sizeDown(){
method size (line 271) | @Override
class Node (line 279) | private class Node{
method Node (line 286) | public Node(Node pre,Node next, Object key, Object val) {
method setUpdateTime (line 294) | public void setUpdateTime(Long updateTime) {
method getUpdateTime (line 298) | public Long getUpdateTime() {
method toString (line 302) | @Override
method hash (line 317) | public int hash(Object key) {
method lruCallback (line 322) | private void lruCallback(){
class CheckTimeThread (line 327) | private class CheckTimeThread implements Runnable{
method run (line 329) | @Override
FILE: src/main/java/com/crossoverjie/actual/LRULinkedMap.java
class LRULinkedMap (line 15) | public class LRULinkedMap<K,V> {
method LRULinkedMap (line 26) | public LRULinkedMap(int cacheSize) {
method put (line 41) | public void put(K key,V value){
method get (line 45) | public V get(K key){
method getAll (line 50) | public Collection<Map.Entry<K, V>> getAll() {
FILE: src/main/java/com/crossoverjie/actual/LRUMap.java
class LRUMap (line 13) | public class LRUMap<K, V> {
method LRUMap (line 37) | public LRUMap(int cacheSize) {
method put (line 56) | public void put(K key, V value) {
method get (line 63) | public V get(K key){
method moveToHead (line 73) | private void moveToHead(Node<K,V> node){
method getNode (line 107) | private Node<K,V> getNode(K key){
method addNode (line 127) | private void addNode(K key, V value) {
method addHead (line 148) | private void addHead(Node<K, V> node) {
method delTail (line 164) | private void delTail() {
class Node (line 176) | private class Node<K, V> {
method Node (line 182) | public Node(K key, V value) {
method Node (line 187) | public Node() {
method getKey (line 190) | public K getKey() {
method setKey (line 194) | public void setKey(K key) {
method getValue (line 198) | public V getValue() {
method setValue (line 202) | public void setValue(V value) {
method toString (line 208) | @Override
FILE: src/main/java/com/crossoverjie/actual/NotifyAll.java
class NotifyAll (line 10) | public class NotifyAll {
method main (line 13) | public static void main(String[] args) throws InterruptedException {
FILE: src/main/java/com/crossoverjie/actual/ReadFile.java
class ReadFile (line 19) | public class ReadFile {
method main (line 36) | public static void main(String[] args) {
method sortAndFindKeyWords (line 56) | private static void sortAndFindKeyWords(File file) throws IOException {
class SortString (line 84) | private static class SortString implements Comparable<SortString>{
method getKey (line 89) | public String getKey() {
method setKey (line 93) | public void setKey(String key) {
method getCount (line 97) | public Integer getCount() {
method setCount (line 101) | public void setCount(Integer count) {
method compareTo (line 106) | @Override
method toString (line 115) | @Override
FILE: src/main/java/com/crossoverjie/actual/Search.java
class Search (line 39) | public class Search {
method main (line 49) | public static void main(String[] args) throws InterruptedException {
method search (line 141) | public static boolean search(String keyWord){
method addWord (line 183) | public static void addWord(String word){
FILE: src/main/java/com/crossoverjie/actual/ThreadCommunication.java
class ThreadCommunication (line 18) | public class ThreadCommunication {
method main (line 21) | public static void main(String[] args) throws Exception {
method cyclicBarrier (line 34) | private static void cyclicBarrier() throws Exception {
method countDownLatch (line 83) | private static void countDownLatch() throws Exception {
method executorService (line 111) | private static void executorService() throws Exception {
method join (line 150) | private static void join() throws InterruptedException {
method piped (line 186) | public static void piped() throws IOException {
FILE: src/main/java/com/crossoverjie/actual/TwoThread.java
class TwoThread (line 15) | public class TwoThread {
method main (line 30) | public static void main(String[] args) {
class OuNum (line 47) | public static class OuNum implements Runnable {
method OuNum (line 51) | public OuNum(TwoThread number) {
method run (line 55) | @Override
class JiNum (line 78) | public static class JiNum implements Runnable {
method JiNum (line 82) | public JiNum(TwoThread number) {
method run (line 86) | @Override
FILE: src/main/java/com/crossoverjie/actual/TwoThreadNonBlocking.java
class TwoThreadNonBlocking (line 17) | public class TwoThreadNonBlocking implements Runnable {
method TwoThreadNonBlocking (line 29) | private TwoThreadNonBlocking(int start, int end, String name) {
method run (line 35) | @Override
method main (line 49) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/actual/TwoThreadWaitNotify.java
class TwoThreadWaitNotify (line 11) | public class TwoThreadWaitNotify {
method main (line 17) | public static void main(String[] args) {
class OuNum (line 34) | public static class OuNum implements Runnable {
method OuNum (line 37) | public OuNum(TwoThreadWaitNotify number) {
method run (line 41) | @Override
class JiNum (line 71) | public static class JiNum implements Runnable {
method JiNum (line 74) | public JiNum(TwoThreadWaitNotify number) {
method run (line 78) | @Override
FILE: src/main/java/com/crossoverjie/actual/TwoThreadWaitNotifySimple.java
class TwoThreadWaitNotifySimple (line 11) | public class TwoThreadWaitNotifySimple {
method main (line 15) | public static void main(String[] args) {
class OuNum (line 32) | public static class OuNum implements Runnable {
method OuNum (line 35) | public OuNum(TwoThreadWaitNotifySimple number) {
method run (line 39) | @Override
class JiNum (line 67) | public static class JiNum implements Runnable {
method JiNum (line 70) | public JiNum(TwoThreadWaitNotifySimple number) {
method run (line 74) | @Override
FILE: src/main/java/com/crossoverjie/algorithm/ArrayKShift.java
class ArrayKShift (line 16) | public class ArrayKShift {
method arrayKShift (line 18) | public void arrayKShift(int[] array, int k) {
method main (line 65) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/algorithm/BinaryNode.java
class BinaryNode (line 12) | public class BinaryNode {
method BinaryNode (line 17) | public BinaryNode() {
method BinaryNode (line 20) | public BinaryNode(Object data, BinaryNode left, BinaryNode right) {
method getData (line 26) | public Object getData() {
method setData (line 30) | public void setData(Object data) {
method getLeft (line 34) | public BinaryNode getLeft() {
method setLeft (line 38) | public void setLeft(BinaryNode left) {
method getRight (line 42) | public BinaryNode getRight() {
method setRight (line 46) | public void setRight(BinaryNode right) {
method createNode (line 51) | public BinaryNode createNode(){
method toString (line 66) | @Override
method levelIterator (line 83) | public void levelIterator(BinaryNode node){
FILE: src/main/java/com/crossoverjie/algorithm/BinaryNodeTravel.java
class BinaryNodeTravel (line 12) | public class BinaryNodeTravel {
method BinaryNodeTravel (line 19) | public BinaryNodeTravel() {
method BinaryNodeTravel (line 22) | public BinaryNodeTravel(Object data, BinaryNodeTravel left, BinaryNode...
method getData (line 28) | public Object getData() {
method setData (line 32) | public void setData(Object data) {
method getLeft (line 36) | public BinaryNodeTravel getLeft() {
method setLeft (line 40) | public void setLeft(BinaryNodeTravel left) {
method getRight (line 44) | public BinaryNodeTravel getRight() {
method setRight (line 48) | public void setRight(BinaryNodeTravel right) {
method createNode (line 53) | public BinaryNodeTravel createNode(){
method toString (line 70) | @Override
method levelIterator (line 90) | public BinaryNodeTravel levelIterator(BinaryNodeTravel node){
FILE: src/main/java/com/crossoverjie/algorithm/BloomFilters.java
class BloomFilters (line 10) | public class BloomFilters {
method BloomFilters (line 22) | public BloomFilters(int arraySize) {
method add (line 31) | public void add(String key) {
method check (line 47) | public boolean check(String key) {
method hashcode_1 (line 77) | private int hashcode_1(String key) {
method hashcode_2 (line 91) | private int hashcode_2(String data) {
method hashcode_3 (line 110) | private int hashcode_3(String key) {
method main (line 124) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/algorithm/HappyNum.java
class HappyNum (line 31) | public class HappyNum {
method isHappy (line 38) | public boolean isHappy(int number) {
method main (line 58) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/algorithm/LinkLoop.java
class LinkLoop (line 11) | public class LinkLoop {
class Node (line 13) | public static class Node{
method Node (line 17) | public Node(Object data, Node next) {
method Node (line 22) | public Node(Object data) {
method isLoop (line 32) | public boolean isLoop(Node node){
FILE: src/main/java/com/crossoverjie/algorithm/LinkedListMergeSort.java
class LinkedListMergeSort (line 13) | public class LinkedListMergeSort {
class Node (line 18) | final static class Node {
method Node (line 22) | public Node() {
method Node (line 25) | public Node(int e, Node next) {
method mergeSort (line 31) | public Node mergeSort(Node first, int length) {
method mergeList (line 75) | public Node mergeList(Node left, Node right) {
method main (line 107) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/algorithm/MergeTwoSortedLists.java
class MergeTwoSortedLists (line 12) | public class MergeTwoSortedLists {
method mergeTwoLists (line 28) | public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
class ListNode (line 54) | public static class ListNode {
method ListNode (line 65) | ListNode(int val) {
method toString (line 69) | @Override
FILE: src/main/java/com/crossoverjie/algorithm/ReverseNode.java
class ReverseNode (line 12) | public class ReverseNode {
method reverseNode1 (line 19) | public void reverseNode1(Node node){
method reverseNode (line 46) | public void reverseNode(Node head) {
method recNode (line 83) | public void recNode(Node node){
class Node (line 96) | public static class Node<T>{
method Node (line 101) | public Node(T value, Node<T> next ) {
FILE: src/main/java/com/crossoverjie/algorithm/TwoArray.java
class TwoArray (line 13) | public class TwoArray {
method main (line 20) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/algorithm/TwoStackQueue.java
class TwoStackQueue (line 18) | public class TwoStackQueue<T> {
method appendTail (line 35) | public void appendTail(T t){
method deleteHead (line 43) | public T deleteHead(){
method getSize (line 57) | public int getSize(){
FILE: src/main/java/com/crossoverjie/algorithm/TwoSum.java
class TwoSum (line 13) | public class TwoSum {
method getTwo1 (line 21) | public int[] getTwo1(int[] nums,int target){
method getTwo2 (line 48) | public int[] getTwo2(int[] nums,int target){
FILE: src/main/java/com/crossoverjie/basic/CollectionsTest.java
class CollectionsTest (line 21) | @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
method arrayList (line 27) | @Benchmark
method arrayListSize (line 40) | @Benchmark
method linkedList (line 52) | @Benchmark
method main (line 65) | public static void main(String[] args) throws RunnerException {
FILE: src/main/java/com/crossoverjie/basic/HashMapTest.java
class HashMapTest (line 15) | public class HashMapTest {
method main (line 16) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/basic/StringTest.java
class StringTest (line 12) | public class StringTest {
method main (line 14) | public static void main(String[] args) throws NoSuchFieldException, Il...
FILE: src/main/java/com/crossoverjie/classloader/ChildClass.java
class ChildClass (line 10) | public class ChildClass extends SuperClass {
FILE: src/main/java/com/crossoverjie/classloader/Main.java
class Main (line 10) | public class Main {
method main (line 11) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/classloader/SuperClass.java
class SuperClass (line 10) | public class SuperClass {
FILE: src/main/java/com/crossoverjie/concurrent/ArrayQueue.java
class ArrayQueue (line 10) | public final class ArrayQueue<T> {
method ArrayQueue (line 43) | public ArrayQueue(int size) {
method put (line 51) | public void put(T t) {
method get (line 83) | public T get() {
method size (line 115) | public synchronized int size() {
method isEmpty (line 124) | public boolean isEmpty() {
FILE: src/main/java/com/crossoverjie/concurrent/CustomThreadPool.java
class CustomThreadPool (line 27) | public class CustomThreadPool {
method CustomThreadPool (line 83) | public CustomThreadPool(int miniSize, int maxSize, long keepAliveTime,
method submit (line 103) | public <T> Future<T> submit(Callable<T> callable) {
method execute (line 115) | public void execute(Runnable runnable) {
method addWorker (line 162) | private void addWorker(Runnable runnable) {
class Worker (line 172) | private final class Worker extends Thread {
method Worker (line 183) | public Worker(Runnable task, boolean isNewTask) {
method startTask (line 189) | public void startTask() {
method close (line 193) | public void close() {
method run (line 197) | @Override
method getTask (line 248) | private Runnable getTask() {
method shutdown (line 291) | public void shutdown() {
method shutDownNow (line 309) | public void shutDownNow() {
method mainNotify (line 318) | public void mainNotify() {
method tryClose (line 339) | private void tryClose(boolean isTry) {
method closeAllTask (line 353) | private void closeAllTask() {
method getWorkerCount (line 365) | public int getWorkerCount() {
class ConcurrentHashSet (line 374) | private final class ConcurrentHashSet<T> extends AbstractSet<T> {
method iterator (line 381) | @Override
method add (line 386) | @Override
method remove (line 392) | @Override
method size (line 398) | @Override
FILE: src/main/java/com/crossoverjie/concurrent/Singleton.java
class Singleton (line 10) | public class Singleton {
method Singleton (line 14) | private Singleton() {
method getInstance (line 17) | public static Singleton getInstance() {
FILE: src/main/java/com/crossoverjie/concurrent/StopThread.java
class StopThread (line 12) | public class StopThread implements Runnable {
method run (line 13) | @Override
method main (line 25) | public static void main(String[] args) throws InterruptedException {
FILE: src/main/java/com/crossoverjie/concurrent/ThreadState.java
class ThreadState (line 12) | public class ThreadState {
method main (line 14) | public static void main(String[] args) {
class TimeWaiting (line 21) | static class TimeWaiting implements Runnable{
method run (line 23) | @Override
class Waiting (line 35) | static class Waiting implements Runnable{
method run (line 37) | @Override
class Blocked (line 52) | static class Blocked implements Runnable{
method run (line 54) | @Override
FILE: src/main/java/com/crossoverjie/concurrent/Volatile.java
class Volatile (line 12) | public class Volatile implements Runnable{
method run (line 16) | @Override
method main (line 23) | public static void main(String[] args) throws InterruptedException {
method stopThread (line 50) | private void stopThread(){
FILE: src/main/java/com/crossoverjie/concurrent/VolatileInc.java
class VolatileInc (line 12) | public class VolatileInc implements Runnable{
method run (line 18) | @Override
method main (line 26) | public static void main(String[] args) throws InterruptedException {
FILE: src/main/java/com/crossoverjie/concurrent/communication/MultipleThreadCountDownKit.java
class MultipleThreadCountDownKit (line 12) | public final class MultipleThreadCountDownKit {
method MultipleThreadCountDownKit (line 26) | public MultipleThreadCountDownKit(int number){
method setNotify (line 38) | public void setNotify(Notify notify){
method countDown (line 46) | public void countDown(){
method await (line 69) | public void await() throws InterruptedException {
FILE: src/main/java/com/crossoverjie/concurrent/communication/Notify.java
type Notify (line 10) | public interface Notify {
method notifyListen (line 15) | void notifyListen() ;
FILE: src/main/java/com/crossoverjie/concurrent/future/Callable.java
type Callable (line 10) | public interface Callable<T> {
method call (line 16) | T call() ;
FILE: src/main/java/com/crossoverjie/concurrent/future/Future.java
type Future (line 10) | public interface Future<T> {
method get (line 17) | T get() throws InterruptedException;
FILE: src/main/java/com/crossoverjie/concurrent/future/FutureTask.java
class FutureTask (line 10) | public class FutureTask<T> implements Runnable,Future<T> {
method FutureTask (line 18) | public FutureTask(Callable<T> callable) {
method get (line 23) | @Override
method run (line 35) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/Main.java
class Main (line 14) | public class Main {
method main (line 15) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/MsgProcessChain.java
class MsgProcessChain (line 13) | public class MsgProcessChain {
method addChain (line 22) | public MsgProcessChain addChain(Process process){
method process (line 31) | public void process(String msg){
FILE: src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/Process.java
type Process (line 10) | public interface Process {
method doProcess (line 16) | void doProcess(String msg) ;
FILE: src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/impl/CopyrightProcess.java
class CopyrightProcess (line 12) | public class CopyrightProcess implements Process {
method doProcess (line 14) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/impl/SensitiveWordProcess.java
class SensitiveWordProcess (line 12) | public class SensitiveWordProcess implements Process {
method doProcess (line 13) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/impl/TypoProcess.java
class TypoProcess (line 12) | public class TypoProcess implements Process {
method doProcess (line 13) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/factorymethod/Animal.java
class Animal (line 10) | public abstract class Animal {
method getName (line 14) | public String getName() {
method setName (line 18) | public void setName(String name) {
method desc (line 25) | protected abstract void desc() ;
FILE: src/main/java/com/crossoverjie/design/pattern/factorymethod/AnimalFactory.java
type AnimalFactory (line 10) | public interface AnimalFactory {
method createAnimal (line 12) | Animal createAnimal() ;
FILE: src/main/java/com/crossoverjie/design/pattern/factorymethod/Cat.java
class Cat (line 10) | public class Cat extends Animal {
method desc (line 11) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/factorymethod/CatFactory.java
class CatFactory (line 10) | public class CatFactory implements AnimalFactory {
method createAnimal (line 11) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/factorymethod/Fish.java
class Fish (line 10) | public class Fish extends Animal {
method desc (line 13) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/factorymethod/FishFactory.java
class FishFactory (line 10) | public class FishFactory implements AnimalFactory {
method createAnimal (line 11) | @Override
FILE: src/main/java/com/crossoverjie/design/pattern/factorymethod/Main.java
class Main (line 10) | public class Main {
method main (line 11) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/disruptor/LongEvent.java
class LongEvent (line 10) | public class LongEvent {
method set (line 13) | public void set(long value) {
method getValue (line 17) | public long getValue() {
method setValue (line 21) | public void setValue(long value) {
FILE: src/main/java/com/crossoverjie/disruptor/LongEventFactory.java
class LongEventFactory (line 12) | public class LongEventFactory implements EventFactory<LongEvent> {
method newInstance (line 13) | @Override
FILE: src/main/java/com/crossoverjie/disruptor/LongEventHandler.java
class LongEventHandler (line 14) | public class LongEventHandler implements EventHandler<LongEvent> {
method onEvent (line 16) | @Override
FILE: src/main/java/com/crossoverjie/disruptor/LongEventMain.java
class LongEventMain (line 18) | public class LongEventMain {
method main (line 19) | public static void main(String[] args) throws Exception {
class Work (line 81) | private static class Work implements Runnable{
method Work (line 86) | public Work(LongEventProducer producer,long bb) {
method run (line 91) | @Override
FILE: src/main/java/com/crossoverjie/disruptor/LongEventProducer.java
class LongEventProducer (line 14) | public class LongEventProducer {
method LongEventProducer (line 18) | public LongEventProducer(RingBuffer<LongEvent> ringBuffer) {
method onData (line 22) | public void onData(long bb) {
FILE: src/main/java/com/crossoverjie/gc/MinorGC.java
class MinorGC (line 10) | public class MinorGC {
method main (line 26) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/guava/CacheLoaderTest.java
class CacheLoaderTest (line 19) | public class CacheLoaderTest {
method init (line 28) | private void init() throws InterruptedException {
method checkAlert (line 50) | private void checkAlert(Integer integer) {
method main (line 68) | public static void main(String[] args) throws InterruptedException {
FILE: src/main/java/com/crossoverjie/guava/callback/CallBackListener.java
type CallBackListener (line 10) | public interface CallBackListener {
method callBackNotify (line 16) | void callBackNotify(String msg) ;
FILE: src/main/java/com/crossoverjie/guava/callback/Caller.java
class Caller (line 13) | public class Caller {
method call (line 26) | public void call(){
method getNotifier (line 46) | public Notifier getNotifier() {
method setNotifier (line 50) | public void setNotifier(Notifier notifier) {
method getCallBackListener (line 55) | public CallBackListener getCallBackListener() {
method setCallBackListener (line 59) | public void setCallBackListener(CallBackListener callBackListener) {
method getQuestion (line 63) | public String getQuestion() {
method setQuestion (line 67) | public void setQuestion(String question) {
method toString (line 71) | @Override
FILE: src/main/java/com/crossoverjie/guava/callback/Main.java
class Main (line 13) | public class Main {
method main (line 17) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/guava/callback/Notifier.java
class Notifier (line 15) | public class Notifier {
method execute (line 19) | public void execute(Caller caller, String msg) throws InterruptedExcep...
FILE: src/main/java/com/crossoverjie/hystrix/CommandOrder.java
class CommandOrder (line 16) | public class CommandOrder extends HystrixCommand<String> {
method CommandOrder (line 22) | public CommandOrder(String orderName) {
method run (line 47) | @Override
FILE: src/main/java/com/crossoverjie/hystrix/CommandTest.java
class CommandTest (line 16) | public class CommandTest {
method main (line 21) | public static void main(String[] args) throws Exception {
FILE: src/main/java/com/crossoverjie/hystrix/CommandUser.java
class CommandUser (line 16) | public class CommandUser extends HystrixCommand<String> {
method CommandUser (line 22) | public CommandUser(String userName) {
method run (line 48) | @Override
FILE: src/main/java/com/crossoverjie/oom/heap/HeapOOM.java
class HeapOOM (line 13) | public class HeapOOM {
method main (line 15) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/oom/heap/MetaSpaceOOM.java
class MetaSpaceOOM (line 16) | public class MetaSpaceOOM {
method main (line 18) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/proxy/cglib/RealSubject.java
class RealSubject (line 13) | public class RealSubject {
method exec (line 16) | public void exec(){
FILE: src/main/java/com/crossoverjie/proxy/cglib/RealSubjectIntercept.java
class RealSubjectIntercept (line 17) | public class RealSubjectIntercept implements MethodInterceptor{
method intercept (line 24) | @Override
FILE: src/main/java/com/crossoverjie/proxy/jdk/CustomizeHandle.java
class CustomizeHandle (line 16) | public class CustomizeHandle implements InvocationHandler {
method CustomizeHandle (line 21) | public CustomizeHandle(Class clazz) {
method invoke (line 31) | @Override
method before (line 43) | private void before() {
method after (line 47) | private void after() {
FILE: src/main/java/com/crossoverjie/proxy/jdk/ISubject.java
type ISubject (line 10) | public interface ISubject {
method execute (line 15) | void execute() ;
FILE: src/main/java/com/crossoverjie/proxy/jdk/impl/ISubjectImpl.java
class ISubjectImpl (line 14) | public class ISubjectImpl implements ISubject {
method execute (line 19) | @Override
FILE: src/main/java/com/crossoverjie/red/RedPacket.java
class RedPacket (line 13) | public class RedPacket {
method splitRedPacket (line 46) | public List<Integer> splitRedPacket(int money, int count) {
method randomRedPacket (line 71) | private int randomRedPacket(int totalMoney, int minMoney, int maxMoney...
method checkMoney (line 119) | private int checkMoney(int lastMoney, int count) {
method main (line 133) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/spring/LifeCycleConfig.java
class LifeCycleConfig (line 13) | @Configuration
method create (line 17) | @Bean(initMethod = "start", destroyMethod = "destroy")
FILE: src/main/java/com/crossoverjie/spring/SpringLifeCycle.java
class SpringLifeCycle (line 17) | public class SpringLifeCycle{
method start (line 20) | public void start(){
method destroy (line 25) | public void destroy(){
FILE: src/main/java/com/crossoverjie/spring/annotation/AnnotationBean.java
class AnnotationBean (line 18) | @Component
method start (line 22) | @PostConstruct
method destroy (line 27) | @PreDestroy
FILE: src/main/java/com/crossoverjie/spring/aware/SpringLifeCycleAware.java
class SpringLifeCycleAware (line 18) | @Component
method setApplicationContext (line 24) | @Override
FILE: src/main/java/com/crossoverjie/spring/processor/SpringLifeCycleProcessor.java
class SpringLifeCycleProcessor (line 17) | @Component
method postProcessBeforeInitialization (line 28) | @Override
method postProcessAfterInitialization (line 43) | @Override
FILE: src/main/java/com/crossoverjie/spring/service/SpringLifeCycleService.java
class SpringLifeCycleService (line 18) | @Service
method afterPropertiesSet (line 21) | @Override
method destroy (line 26) | @Override
FILE: src/main/java/com/crossoverjie/synchronize/Synchronize.java
class Synchronize (line 10) | public class Synchronize {
method main (line 12) | public static void main(String[] args) {
FILE: src/main/java/com/crossoverjie/thread/ThreadExceptionTest.java
class ThreadExceptionTest (line 18) | public class ThreadExceptionTest {
method main (line 23) | public static void main(String[] args) throws InterruptedException {
class Run1 (line 49) | private static class Run1 implements Runnable {
method run (line 51) | @Override
class Run2 (line 74) | private static class Run2 implements Runnable {
method Run2 (line 76) | public Run2() {
method run (line 80) | @Override
FILE: src/test/java/com/crossoverjie/actual/AbstractMapTest.java
class AbstractMapTest (line 7) | public class AbstractMapTest {
method test (line 11) | @Test
method main (line 24) | public static void main(String[] args) {
FILE: src/test/java/com/crossoverjie/actual/LRULinkedMapTest.java
class LRULinkedMapTest (line 9) | public class LRULinkedMapTest {
method put (line 10) | @Test
method put2 (line 28) | @Test
method get (line 46) | @Test
FILE: src/test/java/com/crossoverjie/actual/LRUMapTest.java
class LRUMapTest (line 5) | public class LRUMapTest {
method put (line 7) | @Test
method put2 (line 22) | @Test
method put3 (line 38) | @Test
method put4 (line 55) | @Test
method get (line 69) | @Test
method get2 (line 85) | @Test
method get3 (line 101) | @Test
method get4 (line 117) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/BinaryNodeTest.java
class BinaryNodeTest (line 5) | public class BinaryNodeTest {
method test1 (line 7) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/BinaryNodeTravelTest.java
class BinaryNodeTravelTest (line 5) | public class BinaryNodeTravelTest {
method levelIterator (line 6) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/BloomFiltersTest.java
class BloomFiltersTest (line 11) | public class BloomFiltersTest {
method guavaTest (line 15) | @Test
method hashMapTest (line 36) | @Test
method bloomFilterTest (line 52) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/HappyNumTest.java
class HappyNumTest (line 6) | public class HappyNumTest {
method isHappy (line 7) | @Test
method isHappy2 (line 14) | @Test
method isHappy3 (line 21) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/LinkLoopTest.java
class LinkLoopTest (line 6) | public class LinkLoopTest {
method isLoop (line 12) | @Test
method isLoop2 (line 30) | @Test
method isLoop3 (line 49) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/LinkedListMergeSortTest.java
class LinkedListMergeSortTest (line 9) | public class LinkedListMergeSortTest {
method constructorOutputVoid (line 13) | @Test
method mergeListNotNull (line 23) | @Test
method mergeListInputNotNullNotNullOutputNotNull2 (line 43) | @Test
method mergeListInputRightNull (line 64) | @Test
method mergeListInputLeftNull (line 83) | @Test
method mergeListInputNull (line 102) | @Test
method mergeSortLength2 (line 117) | @Test
method mergeSortInputNull (line 138) | @Test
method mainInput0OutputVoid (line 152) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/MergeTwoSortedListsTest.java
class MergeTwoSortedListsTest (line 8) | public class MergeTwoSortedListsTest {
method setUp (line 10) | @Before
method mergeTwoLists (line 15) | @Test
method mergeTwoLists2 (line 51) | @Test
method mergeTwoLists3 (line 68) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/ReverseNodeTest.java
class ReverseNodeTest (line 6) | public class ReverseNodeTest {
method reverseNode1 (line 8) | @Test
method reverseNode12 (line 19) | @Test
method reverseNode13 (line 28) | @Test
method reverseHead21 (line 42) | @Test
method recNodeTest31 (line 55) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/TwoStackQueueTest.java
class TwoStackQueueTest (line 7) | public class TwoStackQueueTest {
method queue (line 9) | @Test
FILE: src/test/java/com/crossoverjie/algorithm/TwoSumTest.java
class TwoSumTest (line 6) | public class TwoSumTest {
method getTwo1 (line 7) | @Test
method getTwo2 (line 16) | @Test
FILE: src/test/java/com/crossoverjie/concurrent/ArrayQueueTest.java
class ArrayQueueTest (line 10) | public class ArrayQueueTest {
method test (line 14) | @Test
method put (line 35) | @Test
method put2 (line 50) | @Test
method put3 (line 75) | @Test
method put4 (line 92) | @Test
method put5 (line 130) | @Test
method put6 (line 159) | @Test
method get2 (line 195) | @Test
FILE: src/test/java/com/crossoverjie/concurrent/CustomThreadPoolExeceptionTest.java
class CustomThreadPoolExeceptionTest (line 12) | public class CustomThreadPoolExeceptionTest {
method execute (line 14) | @Test
method main (line 19) | public static void main(String[] args) throws InterruptedException {
class Worker (line 37) | private static class Worker implements Runnable {
method Worker (line 41) | public Worker(int state) {
method run (line 45) | @Override
FILE: src/test/java/com/crossoverjie/concurrent/CustomThreadPoolFutureTest.java
class CustomThreadPoolFutureTest (line 16) | public class CustomThreadPoolFutureTest {
method execute (line 18) | @Test
method main (line 23) | public static void main(String[] args) throws InterruptedException {
class Worker (line 54) | private static class Worker implements Callable<Integer> {
method Worker (line 58) | public Worker(int state) {
method call (line 62) | @Override
FILE: src/test/java/com/crossoverjie/concurrent/CustomThreadPoolTest.java
class CustomThreadPoolTest (line 12) | public class CustomThreadPoolTest {
method execute (line 14) | @Test
method main (line 19) | public static void main(String[] args) throws InterruptedException {
class Worker (line 52) | private static class Worker implements Runnable{
method Worker (line 56) | public Worker(int state) {
method run (line 60) | @Override
FILE: src/test/java/com/crossoverjie/concurrent/MultipleThreadCountDownKitTest.java
class MultipleThreadCountDownKitTest (line 9) | public class MultipleThreadCountDownKitTest {
method main (line 14) | public static void main(String[] args) throws InterruptedException {
FILE: src/test/java/com/crossoverjie/concurrent/ThreadPoolTest.java
class ThreadPoolTest (line 11) | public class ThreadPoolTest {
method main (line 16) | public static void main(String[] args) throws Exception {
class Worker (line 37) | private static class Worker implements Callable<Integer>{
method Worker (line 41) | public Worker(int state) {
method call (line 45) | @Override
FILE: src/test/java/com/crossoverjie/kafka/KafkaTest.java
class KafkaTest (line 20) | public class KafkaTest {
method consumer (line 23) | @Test
method threadConsumer (line 50) | @Test
class Consumer (line 79) | private class Consumer extends Thread{
method Consumer (line 86) | public Consumer(TopicPartition topicPartition, Properties props,Stri...
method run (line 94) | @Override
FILE: src/test/java/com/crossoverjie/proxy/JDKProxyTest.java
class JDKProxyTest (line 17) | public class JDKProxyTest {
method test (line 19) | @Test
method clazzTest (line 26) | @Test
FILE: src/test/java/com/crossoverjie/red/RedPacketTest.java
class RedPacketTest (line 7) | public class RedPacketTest {
method right (line 9) | @Test
method right_ (line 22) | @Test
method right__ (line 35) | @Test
FILE: src/test/java/com/crossoverjie/reference/ReferenceTest.java
class ReferenceTest (line 15) | public class ReferenceTest {
method testBasic (line 17) | @Test
method modifyBasic (line 25) | private void modifyBasic(int aa) {
method testReference01 (line 31) | @Test
method modifyCar1 (line 38) | private void modifyCar1(Car car){
method testList (line 44) | @Test
method addList (line 52) | private void addList(List<Integer> list) {
method test02 (line 56) | @Test
method modifyCar (line 63) | private void modifyCar(Car car2) {
class Car (line 69) | private class Car{
method Car (line 72) | public Car(String name) {
method toString (line 76) | @Override
method testReference03 (line 84) | @Test
method modifyReference02 (line 93) | private void modifyReference02(List<Integer> aa) {
Condensed preview — 221 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (727K chars).
[
{
"path": ".github/ISSUE_TEMPLATE",
"chars": 122,
"preview": "**在提交issue之前请回答以下问题,谢谢!**\n\n> 建议首先查看是否已经有类似的 Issues (提交时可删除该提示)\n\n### 你使用的是哪个版本\n\n### 预期结果\n\n### 实际结果\n\n### 重现结果的步骤\n\n### 其他相关"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE",
"chars": 353,
"preview": "如果是文字类 PR,请按照 [中文排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) 进行编写(提交时可删除该提示)。\n\n**What kind of cha"
},
{
"path": ".travis.yml",
"chars": 155,
"preview": "language: java\n\ninstall: mvn install -DskipTests=true -Dmaven.javadoc.skip=true\nscript: mvn -DskipTests=true clean insta"
},
{
"path": "79884.log",
"chars": 5020,
"preview": "2018-03-06 23:11:05\nFull thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):\n\n\"Attach Listener\" #15 d"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2017 crossoverJie\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "MD/ArrayList.md",
"chars": 3949,
"preview": "# ArrayList/Vector 的底层分析\n\n## ArrayList\n\n`ArrayList` 实现于 `List`、`RandomAccess` 接口。可以插入空数据,也支持随机访问。\n\n`ArrayList `相当于动态数据,其"
},
{
"path": "MD/Cache-design.md",
"chars": 909,
"preview": "# 分布式缓存设计\n\n目前常见的缓存方案都是分层缓存,通常可以分为以下几层:\n\n- `NG` 本地缓存,命中的话直接返回。\n- `NG` 没有命中时则需要查询分布式缓存,如 `Redis` 。\n- 如果分布式缓存没有命中则需要回源到 `To"
},
{
"path": "MD/ClassLoad.md",
"chars": 450,
"preview": "# 类加载机制\n\n## 双亲委派模型\n\n模型如下图:\n\n\n\n双亲委派模型中除了启动类加载器之外其余都"
},
{
"path": "MD/ConcurrentHashMap.md",
"chars": 2905,
"preview": "**更多 HashMap 与 ConcurrentHashMap 相关请查看[这里](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)。**\n\n# Con"
},
{
"path": "MD/Consistent-Hash.md",
"chars": 1496,
"preview": "# 一致 Hash 算法\n\n当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题:\n\n如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。\n\n## Hash 取模\n随机放置就不说了,会带来很多问题"
},
{
"path": "MD/DB-split.md",
"chars": 832,
"preview": "# 数据库水平垂直拆分\n\n当数据库量非常大的时候,DB 已经成为系统瓶颈时就可以考虑进行水平垂直拆分了。\n\n## 水平拆分\n\n一般水平拆分是根据表中的某一字段(通常是主键 ID )取模处理,将一张表的数据拆分到多个表中。这样每张表的表结构是"
},
{
"path": "MD/GarbageCollection.md",
"chars": 1537,
"preview": "# 垃圾回收\n\n> 垃圾回收主要思考三件事情:\n\n- 哪种内存需要回收?\n- 什么时候回收?\n- 怎么回收?\n\n## 对象是否存活\n\n### 引用计数法\n\n这是一种非常简单易理解的回收算法。每当有一个地方引用一个对象的时候则在引用计数器上 "
},
{
"path": "MD/HashMap.md",
"chars": 2028,
"preview": "**更多 HashMap 与 ConcurrentHashMap 相关请查看[这里](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)。**\n\n# Has"
},
{
"path": "MD/ID-generator.md",
"chars": 799,
"preview": "# 分布式 ID 生成器\n\n一个唯一 ID 在一个分布式系统中是非常重要的一个业务属性,其中包括一些如订单 ID,消息 ID ,会话 ID,他们都有一些共有的特性:\n\n- 全局唯一。\n- 趋势递增。\n\n全局唯一很好理解,目的就是唯一标识某个"
},
{
"path": "MD/Java-lock.md",
"chars": 1000,
"preview": "# 对锁的一些认知 有哪些锁\n\n## 同一进程\n\n### [重入锁](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ReentrantLock.md)\n使用 `R"
},
{
"path": "MD/Limiting.md",
"chars": 1481,
"preview": "# 限流算法\n\n限流是解决高并发大流量的一种方案,至少是可以保证应用的可用性。\n\n通常有以下两种限流方案:\n\n- 漏桶算法\n- 令牌桶算法\n\n## 漏桶算法\n\n\n\n如图所示 `LinkedList` 底层是基于双向链表实现的,也是实现了 `List"
},
{
"path": "MD/MemoryAllocation.md",
"chars": 2008,
"preview": "# Java 运行时的内存划分\n\n\n\n## 程序计数器\n\n记录当前线程所执行的字节码行号,用于获取下"
},
{
"path": "MD/MySQL-Index.md",
"chars": 805,
"preview": "# MySQL 索引原理\n\n现在互联网应用中对数据库的使用多数都是读较多,比例可以达到 `10:1`。并且数据库在做查询时 `IO` 消耗较大,所以如果能把一次查询的 `IO` 次数控制在常量级那对数据库的性能提升将是非常明显的,因此基于 "
},
{
"path": "MD/OOM-analysis.md",
"chars": 3300,
"preview": "# OOM 分析\n\n## Java 堆内存溢出\n\n在 Java 堆中只要不断的创建对象,并且 `GC-Roots` 到对象之间存在引用链,这样 `JVM` 就不会回收对象。\n\n只要将`-Xms(最小堆)`,`-Xmx(最大堆)` 设置为一样"
},
{
"path": "MD/ReentrantLock.md",
"chars": 7153,
"preview": "# ReentrantLock 实现原理 \n\n使用 `synchronized` 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。\n\n而 `ReentrantLock` 就是一个普通的类,它是基于 "
},
{
"path": "MD/SQL-optimization.md",
"chars": 1293,
"preview": "# SQL 优化\n\n### 负向查询不能使用索引\n\n```sql\nselect name from user where id not in (1,3,4);\n```\n应该修改为:\n\n```\nselect name from user wh"
},
{
"path": "MD/Spike.md",
"chars": 894,
"preview": "# 设计一个秒杀系统\n\n**具体实现参考 [秒杀架构实践](https://crossoverjie.top/2018/05/07/ssm/SSM18-seconds-kill/)**\n\n主要做到以下两点:\n\n- 尽量将请求过滤在上游。\n-"
},
{
"path": "MD/SpringAOP.md",
"chars": 5943,
"preview": "# Spring AOP 实现原理\n\n## 静态代理\n\n众所周知 Spring 的 `AOP` 是基于动态代理实现的,谈到动态代理就不得不提下静态代理。实现如下:\n\n假设有一接口 `InterfaceA`:\n\n```java\npublic "
},
{
"path": "MD/Synchronize.md",
"chars": 2929,
"preview": "# synchronized 关键字原理\n\n众所周知 `synchronized` 关键字是解决并发问题常用解决方案,有以下三种使用方式:\n\n- 同步普通方法,锁的是当前对象。\n- 同步静态方法,锁的是当前 `Class` 对象。\n- 同步"
},
{
"path": "MD/TCP-IP.md",
"chars": 458,
"preview": "# TCP/IP 协议\n\n`TCP/IP` 总结起来就三个要点\n- 三次握手的意义。\n- 超时重发。\n- 滑动窗口。\n\n## 三次握手\n\n\n"
},
{
"path": "MD/Threadcore.md",
"chars": 3211,
"preview": "# Java 多线程三大核心\n\n## 原子性\n`Java` 的原子性就和数据库事务的原子性差不多,一个操作中要么全部执行成功或者失败。\n\n`JMM` 只是保证了基本的原子性,但类似于 `i++` 之类的操作,看似是原子操作,其实里面涉及到:"
},
{
"path": "MD/additional-skills/how-to-use-git-efficiently.md",
"chars": 3062,
"preview": "\n# 【译】如何高效的使用 Git\n\n[原文链接](https://medium.freecodecamp.org/how-to-use-git-efficiently-54320a236369)\n\n\n\n# 前言\n\n"
},
{
"path": "MD/collection/HashSet.md",
"chars": 989,
"preview": "# HashSet\n\n`HashSet` 是一个不允许存储重复元素的集合,它的实现比较简单,只要理解了 `HashMap`,`HashSet` 就水到渠成了。\n\n## 成员变量\n首先了解下 `HashSet` 的成员变量:\n\n```java"
},
{
"path": "MD/collection/LinkedHashMap.md",
"chars": 6838,
"preview": "# LinkedHashMap 底层分析\n\n众所周知 [HashMap](https://github.com/crossoverJie/Java-Interview/blob/master/MD/HashMap.md) 是一个无序的 `M"
},
{
"path": "MD/concurrent/thread-communication.md",
"chars": 15304,
"preview": "# 深入理解线程通信\n\n## 前言\n\n开发中不免会遇到需要所有子线程执行完毕通知主线程处理某些逻辑的场景。\n\n或者是线程 A 在执行到某个条件通知线程 B 执行某个操作。\n\n可以通过以下几种方式实现:\n\n\n## 等待通知机制\n> 等待通知模"
},
{
"path": "MD/concurrent/volatile.md",
"chars": 4537,
"preview": "# 你应该知道的 volatile 关键字\n\n## 前言\n\n不管是在面试还是实际开发中 `volatile` 都是一个应该掌握的技能。\n\n首先来看看为什么会出现这个关键字。\n\n## 内存可见性\n由于 `Java` 内存模型(`JMM`)规定"
},
{
"path": "MD/distributed/Distributed-Limit.md",
"chars": 11275,
"preview": "\n\n## 前言\n\n本文接着上文[应用限流](http://crossoverjie.top/2017"
},
{
"path": "MD/distributed/distributed-lock-redis.md",
"chars": 6748,
"preview": "\n\n## 前言\n分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的"
},
{
"path": "MD/jvm/OOM-Disruptor.md",
"chars": 2821,
"preview": "\n\n# 前言\n\n`OutOfMemoryError` 问题相信很多朋友都遇到过,相对于常见的业务异常"
},
{
"path": "MD/kafka/kafka-product.md",
"chars": 6372,
"preview": "\n# 从源码分析如何优雅的使用 Kafka 生产者\n\n\n\n\n# 前言\n\n在上文 [设计一个百万级的消"
},
{
"path": "MD/newObject.md",
"chars": 2202,
"preview": "# 对象的创建与内存分配\n\n\n## 创建对象\n\n当 `JVM` 收到一个 `new` 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被[加载](https://github.com/crossoverJie/J"
},
{
"path": "MD/soft-skills/Interview-experience.md",
"chars": 6039,
"preview": "\n\n## 前言\n\n最近有些朋友在面试阿里,加上 [Java-Interview](https://g"
},
{
"path": "MD/soft-skills/how-to-be-developer.md",
"chars": 8709,
"preview": "\n\n## 前言\n\n已经记不清有多少读者问过:\n\n> 博主,你是怎么学习的?像我这样的情况有啥好的建议"
},
{
"path": "MD/spring/spring-bean-lifecycle.md",
"chars": 5739,
"preview": "## Spring Bean 生命周期\n\n\n### 前言\n\nSpring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。\n\n首先看下生命周期图:\n\n\n\n## 前言\n\nGoogle 出的 [Guava](https://github.com/google/guava) 是"
},
{
"path": "MD/third-party-component/seconds-kill.md",
"chars": 16543,
"preview": "\n\n## 前言\n\n之前在 [JCSprout](https://github.com/crossov"
},
{
"path": "README.md",
"chars": 7530,
"preview": "\n<div align=\"center\"> \n\n<img src=\"https://s2.loli.net/2024/05/17/D8brxzCPh5vpOFk.png\" width=\"\"/> \n<br/>\n\n["
},
{
"path": "docs/_coverpage.md",
"chars": 195,
"preview": "\n\n<img src=\"_media/icon-left-font-monochrome-black.png\" width=\"400\" />\n\n\n> `Java Core Sprout`:处于萌芽阶段的 Java 核心知识库。\n\n[Git"
},
{
"path": "docs/_sidebar.md",
"chars": 2669,
"preview": "- 集合\n\n - [ArrayList/Vector](collections/ArrayList.md)\n - [LinkedList](collections/LinkedList.md)\n - [HashMap](collect"
},
{
"path": "docs/algorithm/Consistent-Hash.md",
"chars": 1406,
"preview": "# 一致 Hash 算法\n\n当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题:\n\n如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。\n\n## Hash 取模\n随机放置就不说了,会带来很多问题"
},
{
"path": "docs/algorithm/LRU-cache.md",
"chars": 17883,
"preview": "\n\n## 前言\nLRU 是 `Least Recently Used` 的简写,字面意思则是`最近最少使用`。\n\n通常用于缓存"
},
{
"path": "docs/algorithm/Limiting.md",
"chars": 1451,
"preview": "# 限流算法\n\n限流是解决高并发大流量的一种方案,至少是可以保证应用的可用性。\n\n通常有以下两种限流方案:\n\n- 漏桶算法\n- 令牌桶算法\n\n## 漏桶算法\n\n\n\n# 前言\n\n记得一年前分享过一篇[《一致性 Hash 算法分析》](https://crossoverjie.top/2018/0"
},
{
"path": "docs/algorithm/guava-bloom-filter.md",
"chars": 8694,
"preview": "\n\n\n# 前言\n\n最近有朋友问我这么一个面试题目:\n\n> 现在有一个非常庞大的数据,假设全是 int 类型。现在我给你一个数,"
},
{
"path": "docs/architecture-design/Spike.md",
"chars": 857,
"preview": "# 设计一个秒杀系统\n\n**具体实现参考 [秒杀架构实践](architecture-design/seconds-kill.md)**\n\n主要做到以下两点:\n\n- 尽量将请求过滤在上游。\n- 尽可能的利用缓存(大多数场景下都是**查多于写"
},
{
"path": "docs/architecture-design/million-sms-push.md",
"chars": 7250,
"preview": "# 设计一个百万级的消息推送系统\n\n\n\n# 前言\n\n"
},
{
"path": "docs/architecture-design/seconds-kill.md",
"chars": 16030,
"preview": "\n\n## 前言\n\n之前在 [JCSprout](architecture-design/Spike.md) 中提到过秒杀架构的设计,这次"
},
{
"path": "docs/collections/ArrayList.md",
"chars": 3949,
"preview": "# ArrayList/Vector 的底层分析\n\n## ArrayList\n\n`ArrayList` 实现于 `List`、`RandomAccess` 接口。可以插入空数据,也支持随机访问。\n\n`ArrayList `相当于动态数据,其"
},
{
"path": "docs/collections/HashMap.md",
"chars": 2038,
"preview": "**更多 HashMap 与 ConcurrentHashMap 相关请查看[这里](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)。**\n\n# Has"
},
{
"path": "docs/collections/HashSet.md",
"chars": 989,
"preview": "# HashSet\n\n`HashSet` 是一个不允许存储重复元素的集合,它的实现比较简单,只要理解了 `HashMap`,`HashSet` 就水到渠成了。\n\n## 成员变量\n首先了解下 `HashSet` 的成员变量:\n\n```java"
},
{
"path": "docs/collections/LinkedHashMap.md",
"chars": 6870,
"preview": "# LinkedHashMap 底层分析\n\n众所周知 [HashMap](https://github.com/crossoverJie/Java-Interview/blob/master/MD/HashMap.md) 是一个无序的 `M"
},
{
"path": "docs/collections/LinkedList.md",
"chars": 1392,
"preview": "# LinkedList 底层分析\n\n\n\n如图所示 `LinkedList` 底层是基于双向链表实现的,也是实现了 `List"
},
{
"path": "docs/contactme.md",
"chars": 793,
"preview": "# SHOW TIME\n\n> 请科学上网\n\n---\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/MAshLFlBRLU\" frameborder="
},
{
"path": "docs/db/DB-split.md",
"chars": 782,
"preview": "# 数据库水平垂直拆分\n\n当数据库量非常大的时候,DB 已经成为系统瓶颈时就可以考虑进行水平垂直拆分了。\n\n## 水平拆分\n\n一般水平拆分是根据表中的某一字段(通常是主键 ID )取模处理,将一张表的数据拆分到多个表中。这样每张表的表结构是"
},
{
"path": "docs/db/MySQL-Index.md",
"chars": 733,
"preview": "# MySQL 索引原理\n\n现在互联网应用中对数据库的使用多数都是读较多,比例可以达到 `10:1`。并且数据库在做查询时 `IO` 消耗较大,所以如果能把一次查询的 `IO` 次数控制在常量级那对数据库的性能提升将是非常明显的,因此基于 "
},
{
"path": "docs/db/SQL-optimization.md",
"chars": 1287,
"preview": "# SQL 优化\n\n## 负向查询不能使用索引\n\n```sql\nselect name from user where id not in (1,3,4);\n```\n应该修改为:\n\n```\nselect name from user whe"
},
{
"path": "docs/db/sharding-db.md",
"chars": 4039,
"preview": "\n\n# 前言\n\n之前不少人问我“能否分享一些分库分表相关的实践”,其实不是我不分享,而是真的经验不多🤣;和大部分人一样都是停留"
},
{
"path": "docs/distributed/Cache-design.md",
"chars": 909,
"preview": "# 分布式缓存设计\n\n目前常见的缓存方案都是分层缓存,通常可以分为以下几层:\n\n- `NG` 本地缓存,命中的话直接返回。\n- `NG` 没有命中时则需要查询分布式缓存,如 `Redis` 。\n- 如果分布式缓存没有命中则需要回源到 `To"
},
{
"path": "docs/distributed/Distributed-Limit.md",
"chars": 11210,
"preview": "\n\n## 前言\n\n本文接着上文[应用限流](http://crossoverjie.top/2017/08/11/sbc4/)"
},
{
"path": "docs/distributed/ID-generator.md",
"chars": 799,
"preview": "# 分布式 ID 生成器\n\n一个唯一 ID 在一个分布式系统中是非常重要的一个业务属性,其中包括一些如订单 ID,消息 ID ,会话 ID,他们都有一些共有的特性:\n\n- 全局唯一。\n- 趋势递增。\n\n全局唯一很好理解,目的就是唯一标识某个"
},
{
"path": "docs/distributed/distributed-lock-redis.md",
"chars": 6722,
"preview": "\n\n## 前言\n分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三。\n\n"
},
{
"path": "docs/frame/SpringAOP.md",
"chars": 5925,
"preview": "# Spring AOP 实现原理\n\n## 静态代理\n\n众所周知 Spring 的 `AOP` 是基于动态代理实现的,谈到动态代理就不得不提下静态代理。实现如下:\n\n假设有一接口 `InterfaceA`:\n\n```java\npublic "
},
{
"path": "docs/frame/guava-cache.md",
"chars": 12076,
"preview": "\n\n## 前言\n\nGoogle 出的 [Guava](https://github.com/google/guava) 是"
},
{
"path": "docs/frame/kafka-consumer.md",
"chars": 3851,
"preview": "\n\n\n\n\n# 前言\n\n之前写过一篇[《从源码分析如何优雅的使用 Kafka 生产者》](https://crossoverji"
},
{
"path": "docs/frame/kafka-product.md",
"chars": 6065,
"preview": "\n# 从源码分析如何优雅的使用 Kafka 生产者\n\n\n\n\n# 前言\n\n在上文 [设计一个百万级的消息推送系统](https:"
},
{
"path": "docs/frame/spring-bean-lifecycle.md",
"chars": 5739,
"preview": "## Spring Bean 生命周期\n\n\n### 前言\n\nSpring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。\n\n首先看下生命周期图:\n\n\n\n双亲委派模型中除了启动类加载器之外其余都需要有自己的父类加载器\n\n"
},
{
"path": "docs/jvm/GarbageCollection.md",
"chars": 1485,
"preview": "# 垃圾回收\n\n> 垃圾回收主要思考三件事情:\n\n- 哪种内存需要回收?\n- 什么时候回收?\n- 怎么回收?\n\n## 对象是否存活\n\n### 引用计数法\n\n这是一种非常简单易理解的回收算法。每当有一个地方引用一个对象的时候则在引用计数器上 "
},
{
"path": "docs/jvm/JVM-concurrent-HashSet-problem.md",
"chars": 3857,
"preview": "\n\n\n\n# 背景\n\n上午刚到公司,准备开始一天的摸鱼之旅时突然收到了一封监控中心的邮件。\n\n心中暗道不好,因为监控系统从来不会"
},
{
"path": "docs/jvm/MemoryAllocation.md",
"chars": 1982,
"preview": "# Java 运行时的内存划分\n\n\n\n## 程序计数器\n\n记录当前线程所执行的字节码行号,用于获取下一条执行的字节码。\n\n当多"
},
{
"path": "docs/jvm/OOM-Disruptor.md",
"chars": 2718,
"preview": "\n\n# 前言\n\n`OutOfMemoryError` 问题相信很多朋友都遇到过,相对于常见的业务异常(数组越界、空指针等)来说"
},
{
"path": "docs/jvm/OOM-analysis.md",
"chars": 3298,
"preview": "# OOM 分析\n\n## Java 堆内存溢出\n\n在 Java 堆中只要不断的创建对象,并且 `GC-Roots` 到对象之间存在引用链,这样 `JVM` 就不会回收对象。\n\n只要将`-Xms(最小堆)`,`-Xmx(最大堆)` 设置为一样"
},
{
"path": "docs/jvm/cpu-percent-100.md",
"chars": 3783,
"preview": "\n\n\n\n# 前言\n\n到了年底果然都不太平,最近又收到了运维报警:表示有些服务器负载非常高,让我们定位问题。\n\n还真是想什么来什"
},
{
"path": "docs/jvm/newObject.md",
"chars": 2190,
"preview": "# 对象的创建与内存分配\n\n\n## 创建对象\n\n当 `JVM` 收到一个 `new` 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被[加载](https://github.com/crossoverJie/J"
},
{
"path": "docs/jvm/volatile.md",
"chars": 4525,
"preview": "# 你应该知道的 volatile 关键字\n\n## 前言\n\n不管是在面试还是实际开发中 `volatile` 都是一个应该掌握的技能。\n\n首先来看看为什么会出现这个关键字。\n\n## 内存可见性\n由于 `Java` 内存模型(`JMM`)规定"
},
{
"path": "docs/netty/Netty(1)TCP-Heartbeat.md",
"chars": 16897,
"preview": "\n\n\n# 前言\n\nNetty 是一个高性能的 NIO 网络框"
},
{
"path": "docs/netty/Netty(2)Thread-model.md",
"chars": 3642,
"preview": "\n\n## 前言\n\n在之前的 [SpringBoot 整合长连接心跳机制](netty/Netty(1)TCP-Heartbea"
},
{
"path": "docs/netty/cicada.md",
"chars": 6852,
"preview": "\n<div align=\"center\"> \n\n<img src=\"https://i.loli.net/2019/07/19/5d31392e3b7b875245.jpg\" /> \n<br/>\n\n[\n\n# 前言\n\n\n大家新年快乐!\n\n新的一年第一篇技术文章希望开个好头,所以元旦三天我也没怎么闲着,希望给大家带来一篇比较"
},
{
"path": "docs/soft-skills/Interview-experience.md",
"chars": 6034,
"preview": "\n\n## 前言\n\n最近有些朋友在面试阿里,加上 [https://github.com/crossoverJie/JCSpro"
},
{
"path": "docs/soft-skills/TCP-IP.md",
"chars": 446,
"preview": "# TCP/IP 协议\n\n`TCP/IP` 总结起来就三个要点\n- 三次握手的意义。\n- 超时重发。\n- 滑动窗口。\n\n## 三次握手\n\n\n## 前言\n\n已经记不清有多少读者问过:\n\n> 博主,你是怎么学习的?像我这样的情况有啥好的建议嘛?\n\n\n也不知道啥时候我"
},
{
"path": "docs/soft-skills/how-to-use-git-efficiently.md",
"chars": 3334,
"preview": "**[原文链接](https://medium.freecodecamp.org/how-to-use-git-efficiently-54320a236369)**\n\n\n\n\n\n# 前言\n\n较长一段时间以来我都发现不少开发者对 jdk 中的 `J.U.C`(java.util.concurren"
},
{
"path": "docs/thread/ConcurrentHashMap.md",
"chars": 2905,
"preview": "**更多 HashMap 与 ConcurrentHashMap 相关请查看[这里](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)。**\n\n# Con"
},
{
"path": "docs/thread/Java-lock.md",
"chars": 1000,
"preview": "# 对锁的一些认知 有哪些锁\n\n## 同一进程\n\n### [重入锁](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ReentrantLock.md)\n使用 `R"
},
{
"path": "docs/thread/ReentrantLock.md",
"chars": 7153,
"preview": "# ReentrantLock 实现原理 \n\n使用 `synchronized` 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。\n\n而 `ReentrantLock` 就是一个普通的类,它是基于 "
},
{
"path": "docs/thread/Synchronize.md",
"chars": 2916,
"preview": "# synchronized 关键字原理\n\n众所周知 `synchronized` 关键字是解决并发问题常用解决方案,有以下三种使用方式:\n\n- 同步普通方法,锁的是当前对象。\n- 同步静态方法,锁的是当前 `Class` 对象。\n- 同步"
},
{
"path": "docs/thread/Thread-common-problem.md",
"chars": 954,
"preview": "# Java 多线程常见问题\n\n## 上下文切换\n多线程并不一定是要在多核处理器才支持的,就算是单核也是可以支持多线程的。\nCPU 通过给每个线程分配一定的时间片,由于时间非常短通常是几十毫秒,所以 CPU 可以不停的切换线程执行任务从而达"
},
{
"path": "docs/thread/ThreadPoolExecutor.md",
"chars": 9356,
"preview": "\n\n## 前言\n\n平时接触过多线程开发的童鞋应该都或多或少了解过线程池,之前发布的《阿里巴巴 Java 手册》里也有一条:\n\n\n\n"
},
{
"path": "docs/thread/Threadcore.md",
"chars": 3216,
"preview": "# Java 多线程三大核心\n\n## 原子性\n`Java` 的原子性就和数据库事务的原子性差不多,一个操作中要么全部执行成功或者失败。\n\n`JMM` 只是保证了基本的原子性,但类似于 `i++` 之类的操作,看似是原子操作,其实里面涉及到:"
},
{
"path": "docs/thread/thread-communication.md",
"chars": 15304,
"preview": "# 深入理解线程通信\n\n## 前言\n\n开发中不免会遇到需要所有子线程执行完毕通知主线程处理某些逻辑的场景。\n\n或者是线程 A 在执行到某个条件通知线程 B 执行某个操作。\n\n可以通过以下几种方式实现:\n\n\n## 等待通知机制\n> 等待通知模"
},
{
"path": "docs/thread/thread-gone.md",
"chars": 4364,
"preview": "\n# 一个线程罢工的诡异事件\n\n\n\n\n# 背景 \n\n事情(事故)是这样的,突然收到报警,线上某个应用里业务逻辑没有执行,导致的"
},
{
"path": "docs/thread/thread-gone2.md",
"chars": 3265,
"preview": "# 线程池中你不容错过的一些细节\n\n\n\n# 背景\n\n上周分享了一篇[《一个线程罢工的诡异事件》](docs/jvm/threa"
},
{
"path": "pom.xml",
"chars": 6019,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www"
},
{
"path": "src/main/java/com/crossoverjie/Application.java",
"chars": 540,
"preview": "package com.crossoverjie;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.Spri"
},
{
"path": "src/main/java/com/crossoverjie/actual/FourThreadPrinter.java",
"chars": 2068,
"preview": "package com.crossoverjie.actual;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent."
},
{
"path": "src/main/java/com/crossoverjie/actual/LRUAbstractMap.java",
"chars": 7776,
"preview": "package com.crossoverjie.actual;\n\nimport com.google.common.util.concurrent.ThreadFactoryBuilder;\nimport org.slf4j.Logger"
},
{
"path": "src/main/java/com/crossoverjie/actual/LRULinkedMap.java",
"chars": 1073,
"preview": "package com.crossoverjie.actual;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.LinkedHashMa"
},
{
"path": "src/main/java/com/crossoverjie/actual/LRUMap.java",
"chars": 4009,
"preview": "package com.crossoverjie.actual;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Function:\n *\n * @author crosso"
},
{
"path": "src/main/java/com/crossoverjie/actual/NotifyAll.java",
"chars": 797,
"preview": "package com.crossoverjie.actual;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2021/7/1 23:08\n * @since JDK 11\n "
},
{
"path": "src/main/java/com/crossoverjie/actual/ReadFile.java",
"chars": 2846,
"preview": "package com.crossoverjie.actual;\n\nimport com.google.common.io.Files;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFac"
},
{
"path": "src/main/java/com/crossoverjie/actual/Search.java",
"chars": 4137,
"preview": "package com.crossoverjie.actual;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2018/10/13 20:00\n * @sinc"
},
{
"path": "src/main/java/com/crossoverjie/actual/ThreadCommunication.java",
"chars": 6351,
"preview": "package com.crossoverjie.actual;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\n"
},
{
"path": "src/main/java/com/crossoverjie/actual/TwoThread.java",
"chars": 2266,
"preview": "package com.crossoverjie.actual;\n\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLoc"
},
{
"path": "src/main/java/com/crossoverjie/actual/TwoThreadNonBlocking.java",
"chars": 1194,
"preview": "package com.crossoverjie.actual;\n\n/**\n * Function: 两个线程交替执行打印 1~100\n * <p>\n * non blocking 版:\n * 两个线程轮询volatile变量(flag) "
},
{
"path": "src/main/java/com/crossoverjie/actual/TwoThreadWaitNotify.java",
"chars": 2657,
"preview": "package com.crossoverjie.actual;\n\n/**\n * Function:两个线程交替执行打印 1~100\n * 等待通知机制版\n *\n * @author crossoverJie\n * Date"
},
{
"path": "src/main/java/com/crossoverjie/actual/TwoThreadWaitNotifySimple.java",
"chars": 2653,
"preview": "package com.crossoverjie.actual;\n\n/**\n * Function:两个线程交替执行打印 1~100\n * 等待通知机制版\n *\n * @author crossoverJie\n * Date: 07/03/"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/ArrayKShift.java",
"chars": 1695,
"preview": "package com.crossoverjie.algorithm;\n\nimport java.util.Arrays;\n\n/**\n * 数组右移K次, 原数组<code> [1, 2, 3, 4, 5, 6, 7]</code> 右移3"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/BinaryNode.java",
"chars": 2352,
"preview": "package com.crossoverjie.algorithm;\n\nimport java.util.LinkedList;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * "
},
{
"path": "src/main/java/com/crossoverjie/algorithm/BinaryNodeTravel.java",
"chars": 2857,
"preview": "package com.crossoverjie.algorithm;\n\nimport java.util.LinkedList;\n\n/**\n * Function: 层序遍历,需要将遍历的节点串联起来\n *\n * @author cros"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/BloomFilters.java",
"chars": 3590,
"preview": "package com.crossoverjie.algorithm;\n\n/**\n * Function:布隆过滤demo\n *\n * @author crossoverJie\n * Date: 2018/11/20 22:"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/HappyNum.java",
"chars": 1363,
"preview": "package com.crossoverjie.algorithm;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Function: 判断一个数字是否为快乐数字 19 "
},
{
"path": "src/main/java/com/crossoverjie/algorithm/LinkLoop.java",
"chars": 1277,
"preview": "package com.crossoverjie.algorithm;\n\n/**\n * Function:是否是环链表,采用快慢指针,一个走的快些一个走的慢些 如果最终相遇了就说明是环\n * 就相当于在一个环形跑道里跑步,速度不一样的最终一"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/LinkedListMergeSort.java",
"chars": 3478,
"preview": "package com.crossoverjie.algorithm;\n\n/**\n * 链表排序, 建议使用归并排序,\n * 问题描述,给定一个Int的链表,要求在时间最优的情况下完成链表元素由大到小的排序,\n * e.g: 1->"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/MergeTwoSortedLists.java",
"chars": 1724,
"preview": "package com.crossoverjie.algorithm;\n\n/**\n * Function: 合并两个排好序的链表\n *\n * 每次比较两个链表的头结点,将较小结点放到新的链表,最后将新链表指向剩余的链表\n *\n * @aut"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/ReverseNode.java",
"chars": 1842,
"preview": "package com.crossoverjie.algorithm;\n\nimport java.util.Stack;\n\n/**\n * Function: 三种方式反向打印单向链表\n *\n * @author crossoverJie\n "
},
{
"path": "src/main/java/com/crossoverjie/algorithm/TwoArray.java",
"chars": 780,
"preview": "package com.crossoverjie.algorithm;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Function: 在二维数组中判断"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/TwoStackQueue.java",
"chars": 997,
"preview": "package com.crossoverjie.algorithm;\n\nimport java.util.Stack;\n\n/**\n * Function: 两个栈实现队列\n *\n * 利用两个栈来实现,第一个栈存放写队列的数据。\n * 第"
},
{
"path": "src/main/java/com/crossoverjie/algorithm/TwoSum.java",
"chars": 1358,
"preview": "package com.crossoverjie.algorithm;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Function:{1,3,5,7} target=8"
},
{
"path": "src/main/java/com/crossoverjie/basic/CollectionsTest.java",
"chars": 1783,
"preview": "package com.crossoverjie.basic;\n\nimport org.openjdk.jmh.annotations.*;\nimport org.openjdk.jmh.runner.Runner;\nimport org."
},
{
"path": "src/main/java/com/crossoverjie/basic/HashMapTest.java",
"chars": 1202,
"preview": "package com.crossoverjie.basic;\n\nimport java.security.Key;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport j"
},
{
"path": "src/main/java/com/crossoverjie/basic/StringTest.java",
"chars": 881,
"preview": "package com.crossoverjie.basic;\n\nimport java.lang.reflect.Field;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * "
},
{
"path": "src/main/java/com/crossoverjie/classloader/ChildClass.java",
"chars": 252,
"preview": "package com.crossoverjie.classloader;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 05/03/2018 23:11\n * "
},
{
"path": "src/main/java/com/crossoverjie/classloader/Main.java",
"chars": 254,
"preview": "package com.crossoverjie.classloader;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 05/03/2018 23:12\n * "
},
{
"path": "src/main/java/com/crossoverjie/classloader/SuperClass.java",
"chars": 318,
"preview": "package com.crossoverjie.classloader;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 05/03/2018 23:11\n * "
},
{
"path": "src/main/java/com/crossoverjie/concurrent/ArrayQueue.java",
"chars": 2123,
"preview": "package com.crossoverjie.concurrent;\n\n/**\n * Function: 数组实现的线程安全阻塞队列\n *\n * @author crossoverJie\n * Date: 2019-04-04 15:0"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/CustomThreadPool.java",
"chars": 9259,
"preview": "package com.crossoverjie.concurrent;\n\nimport com.crossoverjie.concurrent.communication.Notify;\nimport com.crossoverjie.c"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/Singleton.java",
"chars": 574,
"preview": "package com.crossoverjie.concurrent;\n\n/**\n * Function:单例模式-双重检查锁\n *\n * @author crossoverJie\n * Date: 09/03/2018 "
},
{
"path": "src/main/java/com/crossoverjie/concurrent/StopThread.java",
"chars": 804,
"preview": "package com.crossoverjie.concurrent;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Function:响应中断\n *\n * @author crossove"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/ThreadState.java",
"chars": 1632,
"preview": "package com.crossoverjie.concurrent;\n\nimport com.crossoverjie.classloader.Main;\n\n/**\n * Function: 线程状态测试\n *\n * @author c"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/Volatile.java",
"chars": 1142,
"preview": "package com.crossoverjie.concurrent;\n\nimport java.util.Scanner;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * "
},
{
"path": "src/main/java/com/crossoverjie/concurrent/VolatileInc.java",
"chars": 1007,
"preview": "package com.crossoverjie.concurrent;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Function:\n *\n * @author "
},
{
"path": "src/main/java/com/crossoverjie/concurrent/communication/MultipleThreadCountDownKit.java",
"chars": 1525,
"preview": "package com.crossoverjie.concurrent.communication;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Function:\n"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/communication/Notify.java",
"chars": 225,
"preview": "package com.crossoverjie.concurrent.communication;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2019-04-17 20:2"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/future/Callable.java",
"chars": 234,
"preview": "package com.crossoverjie.concurrent.future;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2019-06-03 23:54\n * @s"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/future/Future.java",
"chars": 290,
"preview": "package com.crossoverjie.concurrent.future;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2019-06-03 23:55\n * @s"
},
{
"path": "src/main/java/com/crossoverjie/concurrent/future/FutureTask.java",
"chars": 807,
"preview": "package com.crossoverjie.concurrent.future;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2019-06-03 23:56\n * @s"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/Main.java",
"chars": 759,
"preview": "package com.crossoverjie.design.pattern.chainofresponsibility;\n\nimport com.crossoverjie.design.pattern.chainofresponsibi"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/MsgProcessChain.java",
"chars": 657,
"preview": "package com.crossoverjie.design.pattern.chainofresponsibility;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/Process.java",
"chars": 273,
"preview": "package com.crossoverjie.design.pattern.chainofresponsibility;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * D"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/impl/CopyrightProcess.java",
"chars": 396,
"preview": "package com.crossoverjie.design.pattern.chainofresponsibility.impl;\n\nimport com.crossoverjie.design.pattern.chainofrespo"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/impl/SensitiveWordProcess.java",
"chars": 400,
"preview": "package com.crossoverjie.design.pattern.chainofresponsibility.impl;\n\nimport com.crossoverjie.design.pattern.chainofrespo"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/chainofresponsibility/impl/TypoProcess.java",
"chars": 391,
"preview": "package com.crossoverjie.design.pattern.chainofresponsibility.impl;\n\nimport com.crossoverjie.design.pattern.chainofrespo"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/factorymethod/Animal.java",
"chars": 414,
"preview": "package com.crossoverjie.design.pattern.factorymethod;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 19/"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/factorymethod/AnimalFactory.java",
"chars": 226,
"preview": "package com.crossoverjie.design.pattern.factorymethod;\n\n/**\n * Function:工厂方法模式\n *\n * @author crossoverJie\n * Dat"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/factorymethod/Cat.java",
"chars": 301,
"preview": "package com.crossoverjie.design.pattern.factorymethod;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 19/"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/factorymethod/CatFactory.java",
"chars": 290,
"preview": "package com.crossoverjie.design.pattern.factorymethod;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 19/"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/factorymethod/Fish.java",
"chars": 305,
"preview": "package com.crossoverjie.design.pattern.factorymethod;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 19/"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/factorymethod/FishFactory.java",
"chars": 293,
"preview": "package com.crossoverjie.design.pattern.factorymethod;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 19/"
},
{
"path": "src/main/java/com/crossoverjie/design/pattern/factorymethod/Main.java",
"chars": 381,
"preview": "package com.crossoverjie.design.pattern.factorymethod;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 19/"
},
{
"path": "src/main/java/com/crossoverjie/disruptor/LongEvent.java",
"chars": 387,
"preview": "package com.crossoverjie.disruptor;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2018/8/29 01:42\n * @si"
},
{
"path": "src/main/java/com/crossoverjie/disruptor/LongEventFactory.java",
"chars": 334,
"preview": "package com.crossoverjie.disruptor;\n\nimport com.lmax.disruptor.EventFactory;\n\n/**\n * Function:\n *\n * @author crossoverJi"
},
{
"path": "src/main/java/com/crossoverjie/disruptor/LongEventHandler.java",
"chars": 605,
"preview": "package com.crossoverjie.disruptor;\n\nimport com.lmax.disruptor.EventHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.L"
},
{
"path": "src/main/java/com/crossoverjie/disruptor/LongEventMain.java",
"chars": 2999,
"preview": "package com.crossoverjie.disruptor;\n\nimport com.google.common.util.concurrent.ThreadFactoryBuilder;\nimport com.lmax.disr"
},
{
"path": "src/main/java/com/crossoverjie/disruptor/LongEventProducer.java",
"chars": 965,
"preview": "package com.crossoverjie.disruptor;\n\nimport com.lmax.disruptor.RingBuffer;\nimport org.slf4j.Logger;\nimport org.slf4j.Log"
},
{
"path": "src/main/java/com/crossoverjie/gc/MinorGC.java",
"chars": 586,
"preview": "package com.crossoverjie.gc;\n\n/**\n * Function: Eden区不够分配时,发生minorGC\n *\n * @author crossoverJie\n * Date: 17/01/20"
},
{
"path": "src/main/java/com/crossoverjie/guava/CacheLoaderTest.java",
"chars": 2824,
"preview": "package com.crossoverjie.guava;\n\nimport com.google.common.cache.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFacto"
},
{
"path": "src/main/java/com/crossoverjie/guava/callback/CallBackListener.java",
"chars": 266,
"preview": "package com.crossoverjie.guava.callback;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 2018/7/15 13:49\n "
},
{
"path": "src/main/java/com/crossoverjie/guava/callback/Caller.java",
"chars": 1668,
"preview": "package com.crossoverjie.guava.callback;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Function:\n *\n"
},
{
"path": "src/main/java/com/crossoverjie/guava/callback/Main.java",
"chars": 743,
"preview": "package com.crossoverjie.guava.callback;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Function:\n *\n"
},
{
"path": "src/main/java/com/crossoverjie/guava/callback/Notifier.java",
"chars": 611,
"preview": "package com.crossoverjie.guava.callback;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.con"
},
{
"path": "src/main/java/com/crossoverjie/hystrix/CommandOrder.java",
"chars": 1535,
"preview": "package com.crossoverjie.hystrix;\n\nimport com.netflix.hystrix.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory"
},
{
"path": "src/main/java/com/crossoverjie/hystrix/CommandTest.java",
"chars": 1003,
"preview": "package com.crossoverjie.hystrix;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent"
},
{
"path": "src/main/java/com/crossoverjie/hystrix/CommandUser.java",
"chars": 1546,
"preview": "package com.crossoverjie.hystrix;\n\nimport com.netflix.hystrix.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory"
},
{
"path": "src/main/java/com/crossoverjie/oom/heap/HeapOOM.java",
"chars": 380,
"preview": "package com.crossoverjie.oom.heap;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Function:堆内存溢出\n *\n * @aut"
},
{
"path": "src/main/java/com/crossoverjie/oom/heap/MetaSpaceOOM.java",
"chars": 906,
"preview": "package com.crossoverjie.oom.heap;\n\nimport net.sf.cglib.proxy.Enhancer;\nimport net.sf.cglib.proxy.MethodInterceptor;\nimp"
},
{
"path": "src/main/java/com/crossoverjie/proxy/cglib/RealSubject.java",
"chars": 376,
"preview": "package com.crossoverjie.proxy.cglib;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Function:\n *\n * "
},
{
"path": "src/main/java/com/crossoverjie/proxy/cglib/RealSubjectIntercept.java",
"chars": 757,
"preview": "package com.crossoverjie.proxy.cglib;\n\nimport net.sf.cglib.proxy.MethodInterceptor;\nimport net.sf.cglib.proxy.MethodProx"
},
{
"path": "src/main/java/com/crossoverjie/proxy/jdk/CustomizeHandle.java",
"chars": 1195,
"preview": "package com.crossoverjie.proxy.jdk;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect."
},
{
"path": "src/main/java/com/crossoverjie/proxy/jdk/ISubject.java",
"chars": 215,
"preview": "package com.crossoverjie.proxy.jdk;\n\n/**\n * Function:\n *\n * @author crossoverJie\n * Date: 23/12/2017 22:37\n * @s"
},
{
"path": "src/main/java/com/crossoverjie/proxy/jdk/impl/ISubjectImpl.java",
"chars": 499,
"preview": "package com.crossoverjie.proxy.jdk.impl;\n\nimport com.crossoverjie.proxy.jdk.ISubject;\nimport org.slf4j.Logger;\nimport or"
},
{
"path": "src/main/java/com/crossoverjie/red/RedPacket.java",
"chars": 3350,
"preview": "package com.crossoverjie.red;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * Function: 模拟微信红包生成,以分为单位\n *\n "
},
{
"path": "src/main/java/com/crossoverjie/spring/LifeCycleConfig.java",
"chars": 533,
"preview": "package com.crossoverjie.spring;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context"
},
{
"path": "src/main/java/com/crossoverjie/spring/SpringLifeCycle.java",
"chars": 675,
"preview": "package com.crossoverjie.spring;\n\nimport com.crossoverjie.Application;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerF"
},
{
"path": "src/main/java/com/crossoverjie/spring/annotation/AnnotationBean.java",
"chars": 659,
"preview": "package com.crossoverjie.spring.annotation;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springf"
},
{
"path": "src/main/java/com/crossoverjie/spring/aware/SpringLifeCycleAware.java",
"chars": 907,
"preview": "package com.crossoverjie.spring.aware;\n\nimport com.crossoverjie.Application;\nimport org.slf4j.Logger;\nimport org.slf4j.L"
},
{
"path": "src/main/java/com/crossoverjie/spring/processor/SpringLifeCycleProcessor.java",
"chars": 1401,
"preview": "package com.crossoverjie.spring.processor;\n\nimport com.crossoverjie.Application;\nimport org.slf4j.Logger;\nimport org.slf"
},
{
"path": "src/main/java/com/crossoverjie/spring/service/SpringLifeCycleService.java",
"chars": 934,
"preview": "package com.crossoverjie.spring.service;\n\nimport com.crossoverjie.spring.SpringLifeCycle;\nimport org.slf4j.Logger;\nimpor"
},
{
"path": "src/main/java/com/crossoverjie/synchronize/Synchronize.java",
"chars": 333,
"preview": "package com.crossoverjie.synchronize;\n\n/**\n * Function:Synchronize 演示\n *\n * @author crossoverJie\n * Date: 02/01/"
},
{
"path": "src/main/java/com/crossoverjie/thread/ThreadExceptionTest.java",
"chars": 1992,
"preview": "package com.crossoverjie.thread;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent."
},
{
"path": "src/main/resources/application.properties",
"chars": 57,
"preview": "spring.application.name=JCSprout\n\nlogging.level.root=INFO"
},
{
"path": "src/main/resources/logback.xml",
"chars": 3275,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n <property name=\"LOG_PATH\" value=\"/Users/chenjie/Documents/log"
},
{
"path": "src/test/java/com/crossoverjie/actual/AbstractMapTest.java",
"chars": 842,
"preview": "package com.crossoverjie.actual;\n\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npubli"
},
{
"path": "src/test/java/com/crossoverjie/actual/LRULinkedMapTest.java",
"chars": 1801,
"preview": "package com.crossoverjie.actual;\n\nimport org.junit.Test;\n\nimport java.util.Map;\n\nimport static org.junit.Assert.*;\n\npubl"
},
{
"path": "src/test/java/com/crossoverjie/actual/LRUMapTest.java",
"chars": 3471,
"preview": "package com.crossoverjie.actual;\n\nimport org.junit.Test;\n\npublic class LRUMapTest {\n\n @Test\n public void put() thr"
}
]
// ... and 21 more files (download for full content)
About this extraction
This page contains the full source code of the crossoverJie/JCSprout GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 221 files (15.7 MB), approximately 249.2k tokens, and a symbol index with 486 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.