├── java_pid26365.hprof ├── src ├── main │ ├── resources │ │ ├── application.properties │ │ └── logback.xml │ └── java │ │ └── com │ │ └── crossoverjie │ │ ├── proxy │ │ ├── jdk │ │ │ ├── ISubject.java │ │ │ ├── impl │ │ │ │ └── ISubjectImpl.java │ │ │ └── CustomizeHandle.java │ │ └── cglib │ │ │ ├── RealSubject.java │ │ │ └── RealSubjectIntercept.java │ │ ├── design │ │ └── pattern │ │ │ └── factorymethod │ │ │ ├── AnimalFactory.java │ │ │ ├── CatFactory.java │ │ │ ├── Cat.java │ │ │ ├── FishFactory.java │ │ │ ├── Fish.java │ │ │ ├── Main.java │ │ │ └── Animal.java │ │ ├── classloader │ │ ├── Main.java │ │ ├── ChildClass.java │ │ └── SuperClass.java │ │ ├── synchronize │ │ └── Synchronize.java │ │ ├── oom │ │ └── heap │ │ │ ├── HeapOOM.java │ │ │ └── MetaSpaceOOM.java │ │ ├── Application.java │ │ ├── concurrent │ │ ├── Singleton.java │ │ ├── StopThread.java │ │ ├── Volatile.java │ │ ├── VolatileInc.java │ │ └── ThreadState.java │ │ ├── gc │ │ └── MinorGC.java │ │ ├── spring │ │ ├── LifeCycleConfig.java │ │ ├── SpringLifeCycle.java │ │ ├── annotation │ │ │ └── AnnotationBean.java │ │ ├── aware │ │ │ └── SpringLifeCycleAware.java │ │ ├── service │ │ │ └── SpringLifeCycleService.java │ │ └── processor │ │ │ └── SpringLifeCycleProcessor.java │ │ ├── algorithm │ │ ├── TwoArray.java │ │ ├── TwoStackQueue.java │ │ ├── LinkLoop.java │ │ ├── TwoSum.java │ │ ├── HappyNum.java │ │ ├── MergeTwoSortedLists.java │ │ ├── ReverseNode.java │ │ └── BinaryNode.java │ │ ├── basic │ │ ├── StringTest.java │ │ └── HashMapTest.java │ │ ├── actual │ │ ├── LRULinkedMap.java │ │ ├── TwoThreadWaitNotify.java │ │ ├── TwoThread.java │ │ ├── ReadFile.java │ │ ├── LRUMap.java │ │ ├── ThreadCommunication.java │ │ └── LRUAbstractMap.java │ │ ├── guava │ │ └── CacheLoaderTest.java │ │ └── red │ │ └── RedPacket.java └── test │ └── java │ └── com │ └── crossoverjie │ ├── algorithm │ ├── BinaryNodeTest.java │ ├── TwoSumTest.java │ ├── HappyNumTest.java │ ├── TwoStackQueueTest.java │ ├── LinkLoopTest.java │ ├── ReverseNodeTest.java │ └── MergeTwoSortedListsTest.java │ ├── actual │ ├── AbstractMapTest.java │ ├── LRULinkedMapTest.java │ └── LRUMapTest.java │ └── proxy │ └── JDKProxyTest.java ├── .travis.yml ├── .github ├── ISSUE_TEMPLATE └── PULL_REQUEST_TEMPLATE ├── MD ├── ClassLoad.md ├── TCP-IP.md ├── MemoryAllocation.md ├── Spike.md ├── MySQL-Index.md ├── ID-generator.md ├── DB-split.md ├── Thread-common-problem.md ├── Cache-design.md ├── Java-lock.md ├── collection │ ├── HashSet.md │ └── LinkedHashMap.md ├── ThreadPoolExecutor.md ├── LinkedList.md ├── SQL-optimization.md ├── Limiting.md ├── Consistent-Hash.md ├── GarbageCollection.md ├── ConcurrentHashMap.md ├── HashMap.md ├── newObject.md ├── Synchronize.md ├── OOM-analysis.md ├── Threadcore.md ├── ArrayList.md ├── concurrent │ ├── volatile.md │ └── thread-communication.md ├── spring │ └── spring-bean-lifecycle.md ├── SpringAOP.md └── ReentrantLock.md ├── LICENSE ├── pom.xml ├── 79884.log └── README.md /java_pid26365.hprof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarLiner/Java-Interview/HEAD/java_pid26365.hprof -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=java-interview 2 | 3 | logging.level.root=INFO -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true 4 | script: mvn -DskipTests=true clean install 5 | 6 | 7 | branches: 8 | only: 9 | - master -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | **在提交issue之前请回答以下问题,谢谢!** 2 | 3 | > 建议首先查看是否已经有类似的 Issues (提交时可删除该提示) 4 | 5 | ### 你使用的是哪个版本 6 | 7 | ### 预期结果 8 | 9 | ### 实际结果 10 | 11 | ### 重现结果的步骤 12 | 13 | ### 其他相关信息 -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/proxy/jdk/ISubject.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.proxy.jdk; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 23/12/2017 22:37 8 | * @since JDK 1.8 9 | */ 10 | public interface ISubject { 11 | 12 | /** 13 | * 执行 14 | */ 15 | void execute() ; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/design/pattern/factorymethod/AnimalFactory.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.design.pattern.factorymethod; 2 | 3 | /** 4 | * Function:工厂方法模式 5 | * 6 | * @author crossoverJie 7 | * Date: 19/03/2018 14:29 8 | * @since JDK 1.8 9 | */ 10 | public interface AnimalFactory { 11 | 12 | Animal createAnimal() ; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/classloader/Main.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.classloader; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 05/03/2018 23:12 8 | * @since JDK 1.8 9 | */ 10 | public class Main { 11 | public static void main(String[] args) { 12 | System.out.println(ChildClass.A); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/classloader/ChildClass.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.classloader; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 05/03/2018 23:11 8 | * @since JDK 1.8 9 | */ 10 | public class ChildClass extends SuperClass { 11 | static { 12 | System.out.println("ChildClass init"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/design/pattern/factorymethod/CatFactory.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.design.pattern.factorymethod; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 19/03/2018 15:21 8 | * @since JDK 1.8 9 | */ 10 | public class CatFactory implements AnimalFactory { 11 | @Override 12 | public Animal createAnimal() { 13 | return new Cat(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/design/pattern/factorymethod/Cat.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.design.pattern.factorymethod; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 19/03/2018 14:33 8 | * @since JDK 1.8 9 | */ 10 | public class Cat extends Animal { 11 | @Override 12 | protected void desc() { 13 | System.out.println("Cat name is=" + this.getName()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/design/pattern/factorymethod/FishFactory.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.design.pattern.factorymethod; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 19/03/2018 15:21 8 | * @since JDK 1.8 9 | */ 10 | public class FishFactory implements AnimalFactory { 11 | @Override 12 | public Animal createAnimal() { 13 | return new Fish() ; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 如果是文字类 PR,请按照 [中文排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) 进行编写(提交时可删除该提示)。 2 | 3 | **What kind of change does this PR introduce?** (check at least one) 4 | 5 | - [ ] Bugfix 6 | - [ ] Feature 7 | - [ ] Code style update 8 | - [ ] Refactor 9 | - [ ] Build-related changes 10 | - [ ] Other, please describe: 11 | 12 | 13 | **The description of the PR:** 14 | 15 | 16 | **Other information:** -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/design/pattern/factorymethod/Fish.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.design.pattern.factorymethod; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 19/03/2018 14:32 8 | * @since JDK 1.8 9 | */ 10 | public class Fish extends Animal { 11 | 12 | 13 | @Override 14 | protected void desc() { 15 | System.out.println("fish name is=" + this.getName()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/algorithm/BinaryNodeTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import org.junit.Test; 4 | 5 | public class BinaryNodeTest { 6 | 7 | @Test 8 | public void test1(){ 9 | BinaryNode node = new BinaryNode() ; 10 | //创建二叉树 11 | node = node.createNode() ; 12 | System.out.println(node); 13 | 14 | //中序遍历二叉树 15 | node.levelIterator(node) ; 16 | 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/classloader/SuperClass.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.classloader; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 05/03/2018 23:11 8 | * @since JDK 1.8 9 | */ 10 | public class SuperClass { 11 | 12 | /** 13 | * 如果使用了 final 修饰的常量,再使用时父类也不会初始化 14 | */ 15 | public static int A = 1; 16 | 17 | static { 18 | System.out.println("SuperClass init"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/synchronize/Synchronize.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.synchronize; 2 | 3 | /** 4 | * Function:Synchronize 演示 5 | * 6 | * @author crossoverJie 7 | * Date: 02/01/2018 13:27 8 | * @since JDK 1.8 9 | */ 10 | public class Synchronize { 11 | 12 | public static void main(String[] args) { 13 | synchronized (Synchronize.class){ 14 | System.out.println("Synchronize"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/proxy/cglib/RealSubject.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.proxy.cglib; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * Function: 8 | * 9 | * @author crossoverJie 10 | * Date: 24/12/2017 19:01 11 | * @since JDK 1.8 12 | */ 13 | public class RealSubject { 14 | private final static Logger LOGGER = LoggerFactory.getLogger(RealSubject.class); 15 | 16 | public void exec(){ 17 | LOGGER.info("real exec"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/design/pattern/factorymethod/Main.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.design.pattern.factorymethod; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 19/03/2018 14:34 8 | * @since JDK 1.8 9 | */ 10 | public class Main { 11 | public static void main(String[] args) { 12 | AnimalFactory factory = new CatFactory() ; 13 | Animal animal = factory.createAnimal(); 14 | animal.setName("猫咪"); 15 | animal.desc(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/oom/heap/HeapOOM.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.oom.heap; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Function:堆内存溢出 8 | * 9 | * @author crossoverJie 10 | * Date: 29/12/2017 18:22 11 | * @since JDK 1.8 12 | */ 13 | public class HeapOOM { 14 | 15 | public static void main(String[] args) { 16 | List list = new ArrayList<>(10) ; 17 | while (true){ 18 | list.add("1") ; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MD/ClassLoad.md: -------------------------------------------------------------------------------- 1 | # 类加载机制 2 | 3 | ## 双亲委派模型 4 | 5 | 模型如下图: 6 | 7 | ![](https://ws3.sinaimg.cn/large/006tNc79ly1fmjwua3iv4j30ic0f0mxq.jpg) 8 | 9 | 双亲委派模型中除了启动类加载器之外其余都需要有自己的父类加载器 10 | 11 | 当一个类收到了类加载请求时: 自己不会首先加载,而是委派给父加载器进行加载,每个层次的加载器都是这样。 12 | 13 | 所以最终每个加载请求都会经过启动类加载器。只有当父类加载返回不能加载时子加载器才会进行加载。 14 | 15 | 双亲委派的好处 : 由于每个类加载都会经过最顶层的启动类加载器,比如 `java.lang.Object`这样的类在各个类加载器下都是同一个类(只有当两个类是由同一个类加载器加载的才有意义,这两个类才相等。) 16 | 17 | 如果没有双亲委派模型,由各个类加载器自行加载的话。当用户自己编写了一个 `java.lang.Object`类,那样系统中就会出现多个 `Object`,这样 Java 程序中最基本的行为都无法保证,程序会变的非常混乱。 -------------------------------------------------------------------------------- /MD/TCP-IP.md: -------------------------------------------------------------------------------- 1 | # TCP/IP 协议 2 | 3 | `TCP/IP` 总结起来就三个要点 4 | - 三次握手的意义。 5 | - 超时重发。 6 | - 滑动窗口。 7 | 8 | ## 三次握手 9 | ![](https://ws4.sinaimg.cn/large/006tNc79gy1fms9a563c3j30o309ogmc.jpg) 10 | 11 | 如图类似: 12 | 1. 发送者问接收者我发消息了,你收到了嘛? 13 | 2. 接收者回复发送者我收到了,你发消息没问题,我收消息也没问题。但我不知道我的发消息有没有问题,你收到了回复我下。 14 | 3. 发送者告诉接收者,我收到你的消息了,你发消息没问题。通信成功我们开始工作吧! 15 | 16 | 17 | ## 超时重发 18 | 19 | 当发送者向接收者发包后,如果过了一段时间(超时时间)依然没有收到消息,就当做本次包丢失,需要重新补发。 20 | 21 | 并且如果一次性发了三个包,只要最后一个包确认收到之后就默认前面两个也收到了。 22 | 23 | ## 滑动窗口 24 | 假设一次性发送包的大小为3,那么每次可以发3个包,而且可以边发边接收,这样就会增强效率。这里的 3 就是滑动窗口的大小,这样的发送方式也叫滑动窗口协议。 -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/design/pattern/factorymethod/Animal.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.design.pattern.factorymethod; 2 | 3 | /** 4 | * Function: 5 | * 6 | * @author crossoverJie 7 | * Date: 19/03/2018 14:29 8 | * @since JDK 1.8 9 | */ 10 | public abstract class Animal { 11 | 12 | private String name ; 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | 22 | /** 23 | * 描述抽象方法 24 | */ 25 | protected abstract void desc() ; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/proxy/jdk/impl/ISubjectImpl.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.proxy.jdk.impl; 2 | 3 | import com.crossoverjie.proxy.jdk.ISubject; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * Function: 9 | * 10 | * @author crossoverJie 11 | * Date: 23/12/2017 22:43 12 | * @since JDK 1.8 13 | */ 14 | public class ISubjectImpl implements ISubject { 15 | private final static Logger LOGGER = LoggerFactory.getLogger(ISubjectImpl.class); 16 | /** 17 | * 执行 18 | */ 19 | @Override 20 | public void execute() { 21 | LOGGER.info("ISubjectImpl execute"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/Application.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | /** 9 | * @author crossoverJie 10 | * 11 | */ 12 | @SpringBootApplication 13 | public class Application { 14 | 15 | private final static Logger LOGGER = LoggerFactory.getLogger(Application.class); 16 | 17 | 18 | public static void main(String[] args) throws Exception { 19 | SpringApplication.run(Application.class, args); 20 | LOGGER.info("start ok!"); 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/algorithm/TwoSumTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.junit.Test; 5 | 6 | public class TwoSumTest { 7 | @Test 8 | public void getTwo1() throws Exception { 9 | TwoSum twoSum = new TwoSum() ; 10 | int[] nums ={1,3,5,7}; 11 | int[] two1 = twoSum.getTwo1(nums, 12); 12 | System.out.println(JSON.toJSONString(two1)); 13 | 14 | } 15 | 16 | @Test 17 | public void getTwo2(){ 18 | TwoSum twoSum = new TwoSum() ; 19 | int[] nums ={1,3,5,7}; 20 | int[] two = twoSum.getTwo2(nums, 10); 21 | System.out.println(JSON.toJSONString(two)); 22 | 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/concurrent/Singleton.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.concurrent; 2 | 3 | /** 4 | * Function:单例模式-双重检查锁 5 | * 6 | * @author crossoverJie 7 | * Date: 09/03/2018 01:14 8 | * @since JDK 1.8 9 | */ 10 | public class Singleton { 11 | 12 | private static volatile Singleton singleton; 13 | 14 | private Singleton() { 15 | } 16 | 17 | public static Singleton getInstance() { 18 | if (singleton == null) { 19 | synchronized (Singleton.class) { 20 | if (singleton == null) { 21 | //防止指令重排 22 | singleton = new Singleton(); 23 | } 24 | } 25 | } 26 | return singleton; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/gc/MinorGC.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.gc; 2 | 3 | /** 4 | * Function: Eden区不够分配时,发生minorGC 5 | * 6 | * @author crossoverJie 7 | * Date: 17/01/2018 22:57 8 | * @since JDK 1.8 9 | */ 10 | public class MinorGC { 11 | 12 | /** 13 | * 1M 14 | */ 15 | private static final int SIZE = 1024 * 1024 ; 16 | 17 | /** 18 | * 19 | -XX:+PrintGCDetails 20 | -Xms20M 21 | -Xmx20M 22 | -Xmn10M 23 | -XX:SurvivorRatio=8 24 | * @param args 25 | */ 26 | public static void main(String[] args) { 27 | byte[] one ; 28 | byte[] four ; 29 | 30 | one = new byte[2 * SIZE] ; 31 | 32 | 33 | //再分配一个 5M 内存时,Eden区不够了, 34 | four = new byte[5 * SIZE] ; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/spring/LifeCycleConfig.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.spring; 2 | 3 | import com.crossoverjie.concurrent.Singleton; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.ComponentScan; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | /** 9 | * Function:使用 initMethod 和 destroyMethod 的方式 10 | * 11 | * @author crossoverJie 12 | * Date: 19/03/2018 22:37 13 | * @since JDK 1.8 14 | */ 15 | @Configuration 16 | public class LifeCycleConfig { 17 | 18 | 19 | @Bean(initMethod = "start", destroyMethod = "destroy") 20 | public SpringLifeCycle create(){ 21 | SpringLifeCycle springLifeCycle = new SpringLifeCycle() ; 22 | 23 | return springLifeCycle ; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MD/MemoryAllocation.md: -------------------------------------------------------------------------------- 1 | # Java 运行时的内存划分 2 | 3 | ![](https://ws1.sinaimg.cn/large/006tNc79ly1fmk5v19cmvj30g20anq3y.jpg) 4 | 5 | ## 程序计数器 6 | 7 | 记录当前线程所执行的字节码行号,用于获取下一条执行的字节码。 8 | 9 | 当多线程运行时,每个线程切换后需要知道上一次所运行的状态、位置。由此也可以看出程序计数器是每个线程**私有**的。 10 | 11 | 12 | ## 虚拟机栈 13 | 虚拟机栈是有一个一个的栈帧组成,栈帧是在每一个方法调用时产生的。 14 | 15 | 每一个栈帧由`局部变量区`、`操作数栈`等组成。每创建一个栈帧压栈,当一个方法执行完毕之后则出栈。 16 | 17 | > 如果出现方法递归调用出现死循环的话就会造成栈帧过多,最终会抛出 `stackoverflow` 异常。 18 | 19 | **这块内存区域也是线程私有的。** 20 | 21 | ## Java 堆 22 | `Java` 堆是整个虚拟机所管理的最大内存区域,所有的对象创建都是在这个区域进行内存分配。 23 | 24 | 这块区域也是垃圾回收器重点管理的区域,由于大多数垃圾回收器都采用`分代回收算法`,所有堆内存也分为 `新生代`、`老年代`,可以方便垃圾的准确回收。 25 | 26 | **这块内存属于线程共享区域。** 27 | 28 | ## 方法区 29 | 30 | 方法区主要用于存放已经被虚拟机加载的类信息,如`常量,静态变量`。 31 | 这块区域也被称为`永久代`。 32 | 33 | ### 运行时常量池 34 | 35 | 运行时常量池是方法区的一部分,其中存放了一些符号引用。当 new 一个对象时,会检查这个区域是否有这个符号的引用。 36 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/spring/SpringLifeCycle.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.spring; 2 | 3 | import com.crossoverjie.Application; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.context.ApplicationContextAware; 9 | 10 | /** 11 | * Function: 12 | * 13 | * @author crossoverJie 14 | * Date: 20/03/2018 18:23 15 | * @since JDK 1.8 16 | */ 17 | public class SpringLifeCycle{ 18 | 19 | private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycle.class); 20 | public void start(){ 21 | LOGGER.info("SpringLifeCycle start"); 22 | } 23 | 24 | 25 | public void destroy(){ 26 | LOGGER.info("SpringLifeCycle destroy"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/algorithm/HappyNumTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class HappyNumTest { 7 | @Test 8 | public void isHappy() throws Exception { 9 | HappyNum happyNum = new HappyNum() ; 10 | boolean happy = happyNum.isHappy(19); 11 | Assert.assertEquals(happy,true); 12 | } 13 | 14 | @Test 15 | public void isHappy2() throws Exception { 16 | HappyNum happyNum = new HappyNum() ; 17 | boolean happy = happyNum.isHappy(11); 18 | Assert.assertEquals(happy,false); 19 | } 20 | 21 | @Test 22 | public void isHappy3() throws Exception { 23 | HappyNum happyNum = new HappyNum() ; 24 | boolean happy = happyNum.isHappy(100); 25 | System.out.println(happy); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /MD/Spike.md: -------------------------------------------------------------------------------- 1 | # 设计一个秒杀系统 2 | 3 | 主要做到以下两点: 4 | 5 | - 尽量将请求过滤在上游。 6 | - 尽可能的利用缓存(大多数场景下都是**查多于写**)。 7 | 8 | 常用的系统分层结构: 9 | 10 | ![](https://ws4.sinaimg.cn/large/006tNc79ly1fmjw06nz2zj306f0fejrh.jpg) 11 | 12 | 针对于浏览器端,可以使用 JS 进行请求过滤,比如五秒钟之类只能点一次抢购按钮,五秒钟只能允许请求一次后端服务。(APP 同理) 13 | 14 | 这样其实就可以过滤掉大部分普通用户。 15 | 16 | 但是防不住直接抓包循环调用。这种情况可以最简单的处理:在`Web层`通过限制一个 UID 五秒之类的请求服务层的次数(可利用 Redis 实现)。 17 | 18 | 但如果是真的有 10W 个不同的 UID 来请求,比如黑客抓肉鸡的方式。 19 | 20 | 这种情况可以在`服务层` 针对于写请求使用请求队列,再通过限流算法([限流算法](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Limiting.md))每秒钟放一部分请求到队列。 21 | 22 | 对于读请求则尽量使用缓存,可以提前将数据准备好,不管是 `Redis` 还是其他缓存中间件效率都是非常高的。 23 | 24 | > ps : 刷新缓存情况,比如库存扣除成功这种情况不用马上刷新缓存,如果库存扣到了 0 再刷新缓存。因为大多数用户都只关心是否有货,并不关心现在还剩余多少。 25 | 26 | ## 总结 27 | 28 | - 如果流量巨大,导致各个层的压力都很大可以适当的加机器横向扩容。如果加不了机器那就只有放弃流量直接返回失败。快速失败非常重要,至少可以保证系统的可用性。 29 | - 业务分批执行:对于下单、付款等操作可以异步执行提高吞吐率。 30 | - 主要目的就是尽量少的请求直接访问到 `DB`。 -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/spring/annotation/AnnotationBean.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.spring.annotation; 2 | 3 | import com.crossoverjie.spring.SpringLifeCycle; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.annotation.PreDestroy; 10 | 11 | /** 12 | * Function:用注解的方法 13 | * 14 | * @author crossoverJie 15 | * Date: 20/03/2018 18:46 16 | * @since JDK 1.8 17 | */ 18 | @Component 19 | public class AnnotationBean { 20 | private final static Logger LOGGER = LoggerFactory.getLogger(AnnotationBean.class); 21 | 22 | @PostConstruct 23 | public void start(){ 24 | LOGGER.info("AnnotationBean start"); 25 | } 26 | 27 | @PreDestroy 28 | public void destroy(){ 29 | LOGGER.info("AnnotationBean destroy"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MD/MySQL-Index.md: -------------------------------------------------------------------------------- 1 | # MySQL 索引原理 2 | 3 | 现在互联网应用中对数据库的使用多数都是读较多,比例可以达到 `10:1`。并且数据库在做查询时 `IO` 消耗较大,所以如果能把一次查询的 `IO` 次数控制在常量级那对数据库的性能提升将是非常明显的,因此基于 `B+ Tree` 的索引结构出现了。 4 | 5 | 6 | ## B+ Tree 的数据结构 7 | 8 | ![](https://ws2.sinaimg.cn/large/006tKfTcgy1fn10d6j9sij30hc08cab3.jpg) 9 | 10 | 如图所示是 `B+ Tree` 的数据结构。是由一个一个的磁盘块组成的树形结构,每个磁盘块由数据项和指针组成。 11 | 12 | > 所有的数据都是存放在叶子节点,非叶子节点不存放数据。 13 | 14 | ## 查找过程 15 | 16 | 以磁盘块1为例,指针 P1 表示小于17的磁盘块,P2 表示在 `17~35` 之间的磁盘块,P3 则表示大于35的磁盘块。 17 | 18 | 比如要查找数据项99,首先将磁盘块1 load 到内存中,发生 1 次 `IO`。接着通过二分查找发现 99 大于 35,所以找到了 P3 指针。通过P3 指针发生第二次 IO 将磁盘块4加载到内存。再通过二分查找发现大于87,通过 P3 指针发生了第三次 IO 将磁盘块11 加载到内存。最后再通过一次二分查找找到了数据项99。 19 | 20 | 由此可见,如果一个几百万的数据查询只需要进行三次 IO 即可找到数据,那么整个效率将是非常高的。 21 | 22 | 观察树的结构,发现查询需要经历几次 IO 是由树的高度来决定的,而树的高度又由磁盘块,数据项的大小决定的。 23 | 24 | 磁盘块越大,数据项越小那么数的高度就越低。这也就是为什么索引字段要尽可能小的原因。 25 | 26 | > 索引使用的一些[原则](https://github.com/crossoverJie/Java-Interview/blob/master/MD/SQL-optimization.md)。 27 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/proxy/cglib/RealSubjectIntercept.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.proxy.cglib; 2 | 3 | import net.sf.cglib.proxy.MethodInterceptor; 4 | import net.sf.cglib.proxy.MethodProxy; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * Function: 12 | * 13 | * @author crossoverJie 14 | * Date: 24/12/2017 19:02 15 | * @since JDK 1.8 16 | */ 17 | public class RealSubjectIntercept implements MethodInterceptor{ 18 | 19 | private final static Logger LOGGER = LoggerFactory.getLogger(RealSubjectIntercept.class); 20 | 21 | 22 | 23 | 24 | @Override 25 | public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 26 | 27 | LOGGER.info("before"); 28 | 29 | Object invoke = methodProxy.invoke(o, objects); 30 | LOGGER.info("after"); 31 | return invoke; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MD/ID-generator.md: -------------------------------------------------------------------------------- 1 | # 分布式 ID 生成器 2 | 3 | 一个唯一 ID 在一个分布式系统中是非常重要的一个业务属性,其中包括一些如订单 ID,消息 ID ,会话 ID,他们都有一些共有的特性: 4 | 5 | - 全局唯一。 6 | - 趋势递增。 7 | 8 | 全局唯一很好理解,目的就是唯一标识某个次请求,某个业务。 9 | 10 | 通常有以下几种方案: 11 | 12 | ## 基于数据库 13 | 可以利用 `MySQL` 中的自增属性 `auto_increment` 来生成全局唯一 ID,也能保证趋势递增。 14 | 但这种方式太依赖 DB,如果数据库挂了那就非常容易出问题。 15 | 16 | ### 水平扩展改进 17 | 但也有改进空间,可以将数据库水平拆分,如果拆为了两个库 A 库和 B 库。 18 | A 库的递增方式可以是 `0 ,2 ,4 ,6`。B 库则是 `1 ,3 ,5 ,7`。这样的方式可以提高系统可用性,并且 ID 也是趋势递增的。 19 | 20 | 但也有如下一下问题: 21 | 22 | - 想要扩容增加性能变的困难,之前已经定义好了 A B 库递增的步数,新加的数据库不好加入进来,水平扩展困难。 23 | - 也是强依赖与数据库,并且如果其中一台挂掉了那就不是绝对递增了。 24 | 25 | ## 本地 UUID 生成 26 | 还可以采用 `UUID` 的方式生成唯一 ID,由于是在本地生成没有了网络之类的消耗,所有效率非常高。 27 | 28 | 但也有以下几个问题: 29 | - 生成的 ID 是无序性的,不能做到趋势递增。 30 | - 由于是字符串并且不是递增,所以不太适合用作主键。 31 | 32 | ## 采用本地时间 33 | 这种做法非常简单,可以利用本地的毫秒数加上一些业务 ID 来生成唯一ID,这样可以做到趋势递增,并且是在本地生成效率也很高。 34 | 35 | 但有一个致命的缺点:当并发量足够高的时候**唯一性**就不能保证了。 36 | 37 | ## Twitter 雪花算法 38 | 39 | 可以基于 `Twitter` 的 `Snowflake` 算法来实现。它主要是一种划分命名空间的算法,将生成的 ID 按照机器、时间等来进行标志。 -------------------------------------------------------------------------------- /MD/DB-split.md: -------------------------------------------------------------------------------- 1 | # 数据库水平垂直拆分 2 | 3 | 当数据库量非常大的时候,DB 已经成为系统瓶颈时就可以考虑进行水平垂直拆分了。 4 | 5 | ## 水平拆分 6 | 7 | 一般水平拆分是根据表中的某一字段(通常是主键 ID )取模处理,将一张表的数据拆分到多个表中。这样每张表的表结构是相同的但是数据不同。 8 | 9 | 不但可以通过 ID 取模分表还可以通过时间分表,比如每月生成一张表。 10 | 按照范围分表也是可行的:一张表只存储 `0~1000W`的数据,超过只就进行分表,这样分表的优点是扩展灵活,但是存在热点数据。 11 | 12 | 按照取模分表拆分之后我们的查询、修改、删除也都是取模。比如新增一条数据的时候往往需要一张临时表来生成 ID,然后根据生成的 ID 取模计算出需要写入的是哪张表(也可以使用[分布式 ID 生成器](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ID-generator.md)来生成 ID)。 13 | 14 | 分表之后不能避免的就是查询要比以前复杂,通常不建议 `join` ,一般的做法是做两次查询。 15 | 16 | ## 垂直拆分 17 | 18 | 当一张表的字段过多时则可以考虑垂直拆分。 19 | 通常是将一张表的字段才分为主表以及扩展表,使用频次较高的字段在一张表,其余的在一张表。 20 | 21 | 这里的多表查询也不建议使用 `join` ,依然建议使用两次查询。 22 | 23 | ## 拆分之后带来的问题 24 | 25 | 拆分之后由一张表变为了多张表,一个库变为了多个库。最突出的一个问题就是事务如何保证。 26 | 27 | ### 两段提交 28 | 29 | ### 最终一致性 30 | 31 | 如果业务对强一致性要求不是那么高那么最终一致性则是一种比较好的方案。 32 | 33 | 通常的做法就是补偿,比如 一个业务是 A 调用 B,两个执行成功才算最终成功,当 A 成功之后,B 执行失败如何来通知 A 呢。 34 | 35 | 比较常见的做法是 失败时 B 通过 MQ 将消息告诉 A,A 再来进行回滚。这种的前提是 A 的回滚操作得是幂等的,不然 B 重复发消息就会出现问题。 -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/TwoArray.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * Function: 在二维数组中判断是否存在查找的数字 8 | * 9 | * @author crossoverJie 10 | * Date: 09/02/2018 22:19 11 | * @since JDK 1.8 12 | */ 13 | public class TwoArray { 14 | 15 | private final static Logger LOGGER = LoggerFactory.getLogger(TwoArray.class); 16 | 17 | 18 | 19 | 20 | public static void main(String[] args) { 21 | int[][] matrix = new int[][]{ 22 | {1, 2, 8, 9}, 23 | {2, 4, 9, 12}, 24 | {4, 7, 10, 13}, 25 | {6, 8, 11, 15}, 26 | {7, 9, 12, 16} 27 | }; 28 | 29 | // 数组的行数 30 | int rows = matrix.length; 31 | // 数组行的列数 32 | int cols = matrix[1].length; 33 | 34 | LOGGER.info(String.valueOf(rows)); 35 | LOGGER.info(String.valueOf(cols)); 36 | 37 | } 38 | 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/concurrent/StopThread.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.concurrent; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Function:响应中断 7 | * 8 | * @author crossoverJie 9 | * Date: 16/03/2018 01:41 10 | * @since JDK 1.8 11 | */ 12 | public class StopThread implements Runnable { 13 | @Override 14 | public void run() { 15 | 16 | while ( !Thread.currentThread().isInterrupted()) { 17 | // 线程执行具体逻辑 18 | System.out.println(Thread.currentThread().getName() + "运行中。。"); 19 | } 20 | 21 | System.out.println(Thread.currentThread().getName() + "退出。。"); 22 | 23 | } 24 | 25 | public static void main(String[] args) throws InterruptedException { 26 | Thread thread = new Thread(new StopThread(), "thread A"); 27 | thread.start(); 28 | 29 | System.out.println("main 线程正在运行") ; 30 | 31 | TimeUnit.MILLISECONDS.sleep(10) ; 32 | thread.interrupt(); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/actual/AbstractMapTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.actual; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class AbstractMapTest { 8 | 9 | private final static Logger LOGGER = LoggerFactory.getLogger(AbstractMapTest.class); 10 | 11 | @Test 12 | public void test(){ 13 | LRUAbstractMap map = new LRUAbstractMap() ; 14 | map.put(1,1) ; 15 | map.put(2,2) ; 16 | 17 | Object o = map.get(1); 18 | LOGGER.info("getSize={}",map.size()); 19 | 20 | map.remove(1) ; 21 | LOGGER.info("getSize"+map.size()); 22 | } 23 | 24 | public static void main(String[] args) { 25 | LRUAbstractMap map = new LRUAbstractMap() ; 26 | map.put(1,1) ; 27 | map.put(2,2) ; 28 | 29 | Object o = map.get(1); 30 | LOGGER.info("getSize={}",map.size()); 31 | 32 | map.remove(1) ; 33 | map.remove(2) ; 34 | LOGGER.info("getSize"+map.size()); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/concurrent/Volatile.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.concurrent; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Function: 7 | * 8 | * @author crossoverJie 9 | * Date: 09/03/2018 00:09 10 | * @since JDK 1.8 11 | */ 12 | public class Volatile implements Runnable{ 13 | 14 | private static volatile boolean flag = true ; 15 | 16 | @Override 17 | public void run() { 18 | while (flag){ 19 | System.out.println(Thread.currentThread().getName() + "正在运行。。。"); 20 | } 21 | System.out.println(Thread.currentThread().getName() +"执行完毕"); 22 | } 23 | 24 | public static void main(String[] args) throws InterruptedException { 25 | Volatile aVolatile = new Volatile(); 26 | new Thread(aVolatile,"thread A").start(); 27 | 28 | 29 | System.out.println("main 线程正在运行") ; 30 | 31 | TimeUnit.MILLISECONDS.sleep(100) ; 32 | 33 | aVolatile.stopThread(); 34 | 35 | } 36 | 37 | private void stopThread(){ 38 | flag = false ; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/spring/aware/SpringLifeCycleAware.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.spring.aware; 2 | 3 | import com.crossoverjie.Application; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.context.ApplicationContextAware; 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * Function: 13 | * 14 | * @author crossoverJie 15 | * Date: 20/03/2018 21:54 16 | * @since JDK 1.8 17 | */ 18 | @Component 19 | public class SpringLifeCycleAware implements ApplicationContextAware { 20 | private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleAware.class); 21 | 22 | private ApplicationContext applicationContext ; 23 | 24 | @Override 25 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 26 | this.applicationContext = applicationContext ; 27 | LOGGER.info("SpringLifeCycleAware start"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/basic/StringTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.basic; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * Function: 7 | * 8 | * @author crossoverJie 9 | * Date: 08/03/2018 13:56 10 | * @since JDK 1.8 11 | */ 12 | public class StringTest { 13 | 14 | public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { 15 | String a = "123"; 16 | //这里的 a 和 b 都是同一个对象,指向同一个字符串常量池对象。 17 | String b = "123" ; 18 | String c = new String("123") ; 19 | 20 | System.out.println("a=b:" + (a == b)); 21 | System.out.println("a=c:" + (a == c)); 22 | 23 | System.out.println("a=" + a); 24 | 25 | a = "456"; 26 | System.out.println("a=" + a); 27 | 28 | 29 | //用反射的方式改变字符串的值 30 | Field value = a.getClass().getDeclaredField("value"); 31 | //改变 value 的访问属性 32 | value.setAccessible(true) ; 33 | 34 | char[] values = (char[]) value.get(a); 35 | values[0] = '9' ; 36 | 37 | System.out.println(a); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/oom/heap/MetaSpaceOOM.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.oom.heap; 2 | 3 | import net.sf.cglib.proxy.Enhancer; 4 | import net.sf.cglib.proxy.MethodInterceptor; 5 | import net.sf.cglib.proxy.MethodProxy; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Function:方法区内存溢出 1.8之后修改为元数据区 11 | * 12 | * @author crossoverJie 13 | * Date: 29/12/2017 21:34 14 | * @since JDK 1.8 15 | */ 16 | public class MetaSpaceOOM { 17 | 18 | public static void main(String[] args) { 19 | while (true){ 20 | Enhancer enhancer = new Enhancer() ; 21 | enhancer.setSuperclass(HeapOOM.class); 22 | enhancer.setUseCache(false) ; 23 | enhancer.setCallback(new MethodInterceptor() { 24 | @Override 25 | public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 26 | return methodProxy.invoke(o,objects) ; 27 | } 28 | }); 29 | enhancer.create() ; 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MD/Thread-common-problem.md: -------------------------------------------------------------------------------- 1 | # Java 多线程常见问题 2 | 3 | ## 上下文切换 4 | 多线程并不一定是要在多核处理器才支持的,就算是单核也是可以支持多线程的。 5 | CPU 通过给每个线程分配一定的时间片,由于时间非常短通常是几十毫秒,所以 CPU 可以不停的切换线程执行任务从而达到了多线程的效果。 6 | 7 | 但是由于在线程切换的时候需要保存本次执行的信息([详见](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 剥夺时间片后又再次运行恢复上次所保存的信息的过程就成为上下文切换。 8 | 9 | > 上下文切换是非常耗效率的。 10 | 11 | 通常有以下解决方案: 12 | - 采用无锁编程,比如将数据按照 `Hash(id)` 进行取模分段,每个线程处理各自分段的数据,从而避免使用锁。 13 | - 采用 CAS(compare and swap) 算法,如 `Atomic` 包就是采用 CAS 算法([详见](https://github.com/crossoverJie/Java-Interview/blob/master/Threadcore.md#%E5%8E%9F%E5%AD%90%E6%80%A7))。 14 | - 合理的创建线程,避免创建了一些线程但其中大部分都是出于 `waiting` 状态,因为每当从 `waiting` 状态切换到 `running` 状态都是一次上下文切换。 15 | 16 | ## 死锁 17 | 18 | 死锁的场景一般是:线程 A 和线程 B 都在互相等待对方释放锁,或者是其中某个线程在释放锁的时候出现异常如死循环之类的。这时就会导致系统不可用。 19 | 20 | 常用的解决方案如下: 21 | 22 | - 尽量一个线程只获取一个锁。 23 | - 一个线程只占用一个资源。 24 | - 尝试使用定时锁,至少能保证锁最终会被释放。 25 | 26 | ## 资源限制 27 | 28 | 当在带宽有限的情况下一个线程下载某个资源需要 `1M/S`,当开 10 个线程时速度并不会乘 10 倍,反而还会增加时间,毕竟上下文切换比较耗时。 29 | 30 | 如果是受限于资源的话可以采用集群来处理任务,不同的机器来处理不同的数据,就类似于开始提到的无锁编程。 31 | -------------------------------------------------------------------------------- /MD/Cache-design.md: -------------------------------------------------------------------------------- 1 | # 分布式缓存设计 2 | 3 | 目前常见的缓存方案都是分层缓存,通常可以分为以下几层: 4 | 5 | - `NG` 本地缓存,命中的话直接返回。 6 | - `NG` 没有命中时则需要查询分布式缓存,如 `Redis` 。 7 | - 如果分布式缓存没有命中则需要回源到 `Tomcat` 在本地堆进行查询,命中之后异步写回 `Redis` 。 8 | - 以上都没有命中那就只有从 `DB` 或者是数据源进行查询,并写回到 Redis 中。 9 | 10 | 11 | ## 缓存更新的原子性 12 | 13 | 在写回 Redis 的时候如果是 `Tomcat` 集群,多个进程同时写那很有可能出现脏数据,这时就会出现更新原子性的问题。 14 | 15 | 可以有以下解决方案: 16 | - 可以将多个 Tomcat 中的数据写入到 MQ 队列中,由消费者进行单线程更新缓存。 17 | - 利用[分布式锁](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),只有获取到锁进程才能写数据。 18 | 19 | ## 如何写缓存 20 | 21 | 写缓存时也要注意,通常来说分为以下几步: 22 | 23 | - 开启事物。 24 | - 写入 DB 。 25 | - 提交事物。 26 | - 写入缓存。 27 | 28 | 这里可能会存在数据库写入成功但是缓存写入失败的情况,但是也不建议将写入缓存加入到事务中。 29 | 因为写缓存的时候可能会因为网络原因耗时较长,这样会阻塞数据库事务。 30 | 如果对一致性要求不高并且数据量也不大的情况下,可以单独起一个服务来做 DB 和缓存之间的数据同步操作。 31 | 32 | 更新缓存时也建议做增量更新。 33 | 34 | ## 负载策略 35 | 36 | 缓存负载策略一般有以下两种: 37 | - 轮询机制。 38 | - 一致哈希算法。 39 | 40 | 轮询的优点是负载到各个服务器的请求是均匀的,但是如果进行扩容则缓存命中率会下降。 41 | 42 | 一致哈希的优点是相同的请求会负载到同一台服务器上,命中率不会随着扩容而降低,但是当大流量过来时有可能把服务器拖垮。 43 | 44 | 所以建议两种方案都采用: 45 | 首先采用一致哈希算法,当流量达到一定的阈值的时候则切换为轮询,这样既能保证缓存命中率,也能提高系统的可用性。 -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/spring/service/SpringLifeCycleService.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.spring.service; 2 | 3 | import com.crossoverjie.spring.SpringLifeCycle; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.DisposableBean; 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.stereotype.Service; 10 | 11 | /** 12 | * Function:实现 InitializingBean DisposableBean 接口 13 | * 14 | * @author crossoverJie 15 | * Date: 20/03/2018 18:38 16 | * @since JDK 1.8 17 | */ 18 | @Service 19 | public class SpringLifeCycleService implements InitializingBean,DisposableBean{ 20 | private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleService.class); 21 | @Override 22 | public void afterPropertiesSet() throws Exception { 23 | LOGGER.info("SpringLifeCycleService start"); 24 | } 25 | 26 | @Override 27 | public void destroy() throws Exception { 28 | LOGGER.info("SpringLifeCycleService destroy"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 crossoverJie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/algorithm/TwoStackQueueTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class TwoStackQueueTest { 8 | private final static Logger LOGGER = LoggerFactory.getLogger(TwoStackQueueTest.class); 9 | @Test 10 | public void queue(){ 11 | TwoStackQueue twoStackQueue = new TwoStackQueue() ; 12 | twoStackQueue.appendTail("1") ; 13 | twoStackQueue.appendTail("2") ; 14 | twoStackQueue.appendTail("3") ; 15 | twoStackQueue.appendTail("4") ; 16 | twoStackQueue.appendTail("5") ; 17 | 18 | 19 | int size = twoStackQueue.getSize(); 20 | 21 | for (int i = 0; i< size ; i++){ 22 | LOGGER.info(twoStackQueue.deleteHead()); 23 | } 24 | 25 | LOGGER.info("========第二次添加========="); 26 | 27 | twoStackQueue.appendTail("6") ; 28 | 29 | size = twoStackQueue.getSize(); 30 | 31 | for (int i = 0; i< size ; i++){ 32 | LOGGER.info(twoStackQueue.deleteHead()); 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /MD/Java-lock.md: -------------------------------------------------------------------------------- 1 | # 对锁的一些认知 有哪些锁 2 | 3 | ## 同一进程 4 | 5 | ### [重入锁](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ReentrantLock.md) 6 | 使用 `ReentrantLock` 获取锁的时候会会判断当前线程是否为获取锁的线程,如果是则将同步的状态 +1 ,释放锁的时候则将状态 -1。只有将同步状态的次数置为 0 的时候才会最终释放锁。 7 | 8 | ### 读写锁 9 | 使用 `ReentrantReadWriteLock` ,同时维护一对锁:读锁和写锁。当写线程访问时则其他所有锁都将阻塞,读线程访问时则不会。通过读写锁的分离可以很大程度的提高并发量和吞吐量。 10 | 11 | 12 | ## 不同进程 13 | 14 | 分布式锁: 15 | 16 | ### 基于数据库 17 | 可以创建一张表,将其中的某个字段设置为`唯一索引`,当多个请求过来的时候只有新建记录成功的请求才算获取到锁,当使用完毕删除这条记录的时候即释放锁。 18 | 19 | 存在的问题: 20 | - 数据库单点问题,挂了怎么办? 21 | - 不是重入锁,同一进程无法在释放锁之前再次获得锁,因为数据库中已经存在了一条记录了。 22 | - 锁是非阻塞的,一旦 `insert` 失败则会立即返回,并不会进入阻塞队列只能下一次再次获取。 23 | - 锁没有失效时间,如果那个进程解锁失败那就没有请求可以再次获取锁了。 24 | 25 | 解决方案: 26 | - 数据库切换为主从,不存在单点。 27 | - 在表中加入一个同步状态字段,每次获取锁的是加 1 ,释放锁的时候`-1`,当状态为 0 的时候就删除这条记录,即释放锁。 28 | - 非阻塞的情况可以用 `while` 循环来实现,循环的时候记录时间,达到 X 秒记为超时,`break`。 29 | - 可以开启一个定时任务每隔一段时间扫描找出多少 X 秒都没有被删除的记录,主动删除这条记录。 30 | 31 | ### 基于 Redis 32 | 33 | 使用 `setNX(key) setEX(timeout)` 命令,只有在该 `key` 不存在的时候创建和这个 `key`,就相当于获取了锁。由于有超时时间,所以过了规定时间会自动删除,这样也可以避免死锁。 34 | 35 | 可以参考: 36 | 37 | [基于 Redis 的分布式锁](http://crossoverjie.top/2018/03/29/distributed-lock/distributed-lock-redis/) 38 | 39 | ### 基于 ZK 40 | -------------------------------------------------------------------------------- /MD/collection/HashSet.md: -------------------------------------------------------------------------------- 1 | # HashSet 2 | 3 | `HashSet` 是一个不允许存储重复元素的集合,它的实现比较简单,只要理解了 `HashMap`,`HashSet` 就水到渠成了。 4 | 5 | ## 成员变量 6 | 首先了解下 `HashSet` 的成员变量: 7 | 8 | ```java 9 | private transient HashMap map; 10 | 11 | // Dummy value to associate with an Object in the backing Map 12 | private static final Object PRESENT = new Object(); 13 | ``` 14 | 15 | 发现主要就两个变量: 16 | 17 | - `map` :用于存放最终数据的。 18 | - `PRESENT` :是所有写入 map 的 `value` 值。 19 | 20 | ## 构造函数 21 | 22 | ```java 23 | public HashSet() { 24 | map = new HashMap<>(); 25 | } 26 | 27 | public HashSet(int initialCapacity, float loadFactor) { 28 | map = new HashMap<>(initialCapacity, loadFactor); 29 | } 30 | ``` 31 | 构造函数很简单,利用了 `HashMap` 初始化了 `map` 。 32 | 33 | ## add 34 | 35 | ```java 36 | public boolean add(E e) { 37 | return map.put(e, PRESENT)==null; 38 | } 39 | ``` 40 | 41 | 比较关键的就是这个 `add()` 方法。 42 | 可以看出它是将存放的对象当做了 `HashMap` 的健,`value` 都是相同的 `PRESENT` 。由于 `HashMap` 的 `key` 是不能重复的,所以每当有重复的值写入到 `HashSet` 时,`value` 会被覆盖,但 `key` 不会收到影响,这样就保证了 `HashSet` 中只能存放不重复的元素。 43 | 44 | ## 总结 45 | 46 | `HashSet` 的原理比较简单,几乎全部借助于 `HashMap` 来实现的。 47 | 48 | 所以 `HashMap` 会出现的问题 `HashSet` 依然不能避免。 49 | 50 | -------------------------------------------------------------------------------- /MD/ThreadPoolExecutor.md: -------------------------------------------------------------------------------- 1 | # 线程池原理分析 2 | 3 | 首先要明确为什么要使用线程池,使用线程池会带来什么好处? 4 | 5 | - 线程是稀缺资源,不能频繁的创建。 6 | - 应当将其放入一个池子中,可以给其他任务进行复用。 7 | - 解耦作用,线程的创建于执行完全分开,方便维护。 8 | 9 | 10 | ## 创建一个线程池 11 | 12 | 以一个使用较多的 13 | 14 | ```java 15 | ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) 16 | ``` 17 | 18 | 为例: 19 | 20 | - 其中的 `corePoolSize` 为线程池的基本大小。 21 | - `maximumPoolSize` 为线程池最大线程大小。 22 | - `keepAliveTime` 和 `unit` 则是线程空闲后的存活时间。 23 | - `workQueue` 用于存放任务的阻塞队列。 24 | - `handler` 当队列和最大线程池都满了之后的饱和策略。 25 | 26 | ## 处理流程 27 | 当提交一个任务到线程池时它的执行流程是怎样的呢? 28 | 29 | ![](https://ws1.sinaimg.cn/large/006tNbRwgy1fnbzmai8yrj30dw08574s.jpg) 30 | 31 | 首先第一步会判断核心线程数有没有达到上限,如果没有则创建线程(会获取全局锁),满了则会将任务丢进阻塞队列。 32 | 33 | 如果队列也满了则需要判断最大线程数是否达到上限,如果没有则创建线程(获取全局锁),如果最大线程数也满了则会根据饱和策略处理。 34 | 35 | 常用的饱和策略有: 36 | - 直接丢弃任务。 37 | - 调用者线程处理。 38 | - 丢弃队列中的最近任务,执行当前任务。 39 | 40 | 所以当线程池完成预热之后都是将任务放入队列,接着由工作线程一个个从队列里取出执行。 41 | 42 | ## 合理配置线程池 43 | 44 | 线程池并不是配置越大越好,而是要根据任务的熟悉来进行划分: 45 | 如果是 `CPU` 密集型任务应当分配较少的线程,比如 `CPU` 个数相当的大小。 46 | 47 | 如果是 IO 密集型任务,由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 `CPU 个数 * 2` 。 48 | 49 | 当是一个混合型任务,可以将其拆分为 `CPU` 密集型任务以及 `IO` 密集型任务,这样来分别配置。 50 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/concurrent/VolatileInc.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.concurrent; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | /** 6 | * Function: 7 | * 8 | * @author crossoverJie 9 | * Date: 09/03/2018 00:34 10 | * @since JDK 1.8 11 | */ 12 | public class VolatileInc implements Runnable{ 13 | 14 | private static volatile int count = 0 ; //使用 volatile 修饰基本数据内存不能保证原子性 15 | 16 | //private static AtomicInteger count = new AtomicInteger() ; 17 | 18 | @Override 19 | public void run() { 20 | for (int i=0;i<10000 ;i++){ 21 | count ++ ; 22 | //count.incrementAndGet() ; 23 | } 24 | } 25 | 26 | public static void main(String[] args) throws InterruptedException { 27 | VolatileInc volatileInc = new VolatileInc() ; 28 | Thread t1 = new Thread(volatileInc,"t1") ; 29 | Thread t2 = new Thread(volatileInc,"t2") ; 30 | t1.start(); 31 | //t1.join(); 32 | 33 | t2.start(); 34 | //t2.join(); 35 | for (int i=0;i<10000 ;i++){ 36 | count ++ ; 37 | //count.incrementAndGet(); 38 | } 39 | 40 | 41 | System.out.println("最终Count="+count); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/TwoStackQueue.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Function: 两个栈实现队列 7 | * 8 | * 利用两个栈来实现,第一个栈存放写队列的数据。 9 | * 第二个栈存放移除队列的数据,移除之前先判断第二个栈里是否有数据。 10 | * 如果没有就要将第一个栈里的数据依次弹出压入第二个栈,这样写入之后的顺序再弹出其实就是一个先进先出的结构了。 11 | * 12 | * 这样出队列只需要移除第二个栈的头元素即可。 13 | * 14 | * @author crossoverJie 15 | * Date: 09/02/2018 23:51 16 | * @since JDK 1.8 17 | */ 18 | public class TwoStackQueue { 19 | 20 | /** 21 | * 写入的栈 22 | */ 23 | private Stack input = new Stack() ; 24 | 25 | /** 26 | * 移除队列所出的栈 27 | */ 28 | private Stack out = new Stack() ; 29 | 30 | 31 | /** 32 | * 写入队列 33 | * @param t 34 | */ 35 | public void appendTail(T t){ 36 | input.push(t) ; 37 | } 38 | 39 | /** 40 | * 删除队列头结点 并返回删除数据 41 | * @return 42 | */ 43 | public T deleteHead(){ 44 | 45 | //是空的 需要将 input 出栈写入 out 46 | if (out.isEmpty()){ 47 | while (!input.isEmpty()){ 48 | out.push(input.pop()) ; 49 | } 50 | } 51 | 52 | //不为空时直接移除出栈就表示移除了头结点 53 | return out.pop() ; 54 | } 55 | 56 | 57 | public int getSize(){ 58 | return input.size() + out.size() ; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/actual/LRULinkedMap.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.actual; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Function: 10 | * 11 | * @author crossoverJie 12 | * Date: 05/04/2018 12:04 13 | * @since JDK 1.8 14 | */ 15 | public class LRULinkedMap { 16 | 17 | 18 | /** 19 | * 最大缓存大小 20 | */ 21 | private int cacheSize; 22 | 23 | private LinkedHashMap cacheMap ; 24 | 25 | 26 | public LRULinkedMap(int cacheSize) { 27 | this.cacheSize = cacheSize; 28 | 29 | cacheMap = new LinkedHashMap(16,0.75F,true){ 30 | @Override 31 | protected boolean removeEldestEntry(Map.Entry eldest) { 32 | if (cacheSize + 1 == cacheMap.size()){ 33 | return true ; 34 | }else { 35 | return false ; 36 | } 37 | } 38 | }; 39 | } 40 | 41 | public void put(K key,V value){ 42 | cacheMap.put(key,value) ; 43 | } 44 | 45 | public V get(K key){ 46 | return cacheMap.get(key) ; 47 | } 48 | 49 | 50 | public Collection> getAll() { 51 | return new ArrayList>(cacheMap.entrySet()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/basic/HashMapTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.basic; 2 | 3 | import java.security.Key; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | 8 | /** 9 | * Function: 10 | * 11 | * @author crossoverJie 12 | * Date: 05/05/2018 12:42 13 | * @since JDK 1.8 14 | */ 15 | public class HashMapTest { 16 | public static void main(String[] args) { 17 | Map map = new HashMap<>(16); 18 | map.put("1", 1); 19 | map.put("2", 2); 20 | map.put("3", 3); 21 | map.put("4", 4); 22 | 23 | Iterator> entryIterator = map.entrySet().iterator(); 24 | while (entryIterator.hasNext()) { 25 | Map.Entry next = entryIterator.next(); 26 | System.out.println("key=" + next.getKey() + " value=" + next.getValue()); 27 | } 28 | System.out.println("============="); 29 | 30 | Iterator iterator = map.keySet().iterator(); 31 | while (iterator.hasNext()){ 32 | String key = iterator.next(); 33 | System.out.println("key=" + key + " value=" + map.get(key)); 34 | 35 | } 36 | 37 | System.out.println("============="); 38 | map.forEach((key, value) -> { 39 | System.out.println("key=" + key + " value=" + map.get(key)); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/proxy/jdk/CustomizeHandle.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.proxy.jdk; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.lang.reflect.InvocationHandler; 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Function: 11 | * 12 | * @author crossoverJie 13 | * Date: 23/12/2017 22:27 14 | * @since JDK 1.8 15 | */ 16 | public class CustomizeHandle implements InvocationHandler { 17 | private final static Logger LOGGER = LoggerFactory.getLogger(CustomizeHandle.class); 18 | 19 | private Object target; 20 | 21 | public CustomizeHandle(Class clazz) { 22 | try { 23 | this.target = clazz.newInstance(); 24 | } catch (InstantiationException e) { 25 | LOGGER.error("InstantiationException", e); 26 | } catch (IllegalAccessException e) { 27 | LOGGER.error("IllegalAccessException",e); 28 | } 29 | } 30 | 31 | @Override 32 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 33 | 34 | before(); 35 | Object result = method.invoke(target, args); 36 | after(); 37 | 38 | LOGGER.info("proxy class={}", proxy.getClass()); 39 | return result; 40 | } 41 | 42 | 43 | private void before() { 44 | LOGGER.info("handle before"); 45 | } 46 | 47 | private void after() { 48 | LOGGER.info("handle after"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/proxy/JDKProxyTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.proxy; 2 | 3 | import com.crossoverjie.proxy.jdk.CustomizeHandle; 4 | import com.crossoverjie.proxy.jdk.ISubject; 5 | import com.crossoverjie.proxy.jdk.impl.ISubjectImpl; 6 | import org.junit.Test; 7 | import sun.misc.ProxyGenerator; 8 | 9 | import java.io.FileNotFoundException; 10 | import java.io.FileOutputStream; 11 | import java.io.IOException; 12 | import java.lang.reflect.Proxy; 13 | 14 | /** 15 | * Function: JDK 代理单测 16 | * 17 | * @author crossoverJie 18 | * Date: 23/12/2017 22:40 19 | * @since JDK 1.8 20 | */ 21 | public class JDKProxyTest { 22 | 23 | @Test 24 | public void test(){ 25 | CustomizeHandle handle = new CustomizeHandle(ISubjectImpl.class) ; 26 | ISubject subject = (ISubject) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{ISubject.class}, handle); 27 | subject.execute() ; 28 | } 29 | 30 | @Test 31 | public void clazzTest(){ 32 | byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 33 | "$Proxy1", new Class[]{ISubject.class}, 1); 34 | try { 35 | FileOutputStream out = new FileOutputStream("/Users/chenjie/Documents/$Proxy1.class") ; 36 | out.write(proxyClassFile); 37 | out.close(); 38 | } catch (FileNotFoundException e) { 39 | e.printStackTrace(); 40 | } catch (IOException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/LinkLoop.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | /** 4 | * Function:是否是环链表,采用快慢指针,一个走的快些一个走的慢些 如果最终相遇了就说明是环 5 | * 就相当于在一个环形跑道里跑步,速度不一样的最终一定会相遇。 6 | * 7 | * @author crossoverJie 8 | * Date: 04/01/2018 11:33 9 | * @since JDK 1.8 10 | */ 11 | public class LinkLoop { 12 | 13 | public static class Node{ 14 | private Object data ; 15 | public Node next ; 16 | 17 | public Node(Object data, Node next) { 18 | this.data = data; 19 | this.next = next; 20 | } 21 | 22 | public Node(Object data) { 23 | this.data = data ; 24 | } 25 | } 26 | 27 | /** 28 | * 判断链表是否有环 29 | * @param node 30 | * @return 31 | */ 32 | public boolean isLoop(Node node){ 33 | Node slow = node ; 34 | Node fast = node.next ; 35 | 36 | while (slow.next != null){ 37 | Object dataSlow = slow.data; 38 | Object dataFast = fast.data; 39 | 40 | //说明有环 41 | if (dataFast == dataSlow){ 42 | return true ; 43 | } 44 | 45 | //一共只有两个节点,但却不是环形链表的情况,判断NPE 46 | if (fast.next == null){ 47 | return false ; 48 | } 49 | //slow走慢点 fast走快点 50 | slow = slow.next ; 51 | fast = fast.next.next ; 52 | 53 | //如果走的快的发现为空 说明不存在环 54 | if (fast == null){ 55 | return false ; 56 | } 57 | } 58 | return false ; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/TwoSum.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Function:{1,3,5,7} target=8 返回{2,3} 8 | * 9 | * @author crossoverJie 10 | * Date: 04/01/2018 09:53 11 | * @since JDK 1.8 12 | */ 13 | public class TwoSum { 14 | 15 | /** 16 | * 时间复杂度为 O(N^2) 17 | * @param nums 18 | * @param target 19 | * @return 20 | */ 21 | public int[] getTwo1(int[] nums,int target){ 22 | int[] result = new int[2] ; 23 | 24 | for (int i= 0 ;i=0 ;j--){ 27 | int b = nums[j] ; 28 | 29 | if ((a+b) == target){ 30 | result = new int[]{i,j} ; 31 | } 32 | } 33 | } 34 | return result ; 35 | } 36 | 37 | 38 | /** 39 | * 时间复杂度 O(N) 40 | * 利用Map Key存放目标值和当前值的差值,value 就是当前的下标 41 | * 每次遍历是 查看当前遍历的值是否等于差值,如果是等于,说明两次相加就等于目标值。 42 | * 然后取出 map 中 value ,和本次遍历的下标,就是两个下标值相加等于目标值了。 43 | * 44 | * @param nums 45 | * @param target 46 | * @return 47 | */ 48 | public int[] getTwo2(int[] nums,int target){ 49 | int[] result = new int[2] ; 50 | Map map = new HashMap<>(2) ; 51 | for (int i=0 ;i l = last; 19 | final Node newNode = new Node<>(l, e, null); 20 | last = newNode; 21 | if (l == null) 22 | first = newNode; 23 | else 24 | l.next = newNode; 25 | size++; 26 | modCount++; 27 | } 28 | ``` 29 | 30 | 可见每次插入都是移动指针,和 ArrayList 的拷贝数组来说效率要高上不少。 31 | 32 | ## 查询方法 33 | 34 | ```java 35 | public E get(int index) { 36 | checkElementIndex(index); 37 | return node(index).item; 38 | } 39 | 40 | Node node(int index) { 41 | // assert isElementIndex(index); 42 | 43 | if (index < (size >> 1)) { 44 | Node x = first; 45 | for (int i = 0; i < index; i++) 46 | x = x.next; 47 | return x; 48 | } else { 49 | Node x = last; 50 | for (int i = size - 1; i > index; i--) 51 | x = x.prev; 52 | return x; 53 | } 54 | } 55 | ``` 56 | 57 | 由此可以看出是使用二分查找来看 `index` 离 size 中间距离来判断是从头结点正序查还是从尾节点倒序查。 58 | 59 | - `node()`会以`O(n/2)`的性能去获取一个结点 60 | - 如果索引值大于链表大小的一半,那么将从尾结点开始遍历 61 | 62 | 这样的效率是非常低的,特别是当 index 越接近 size 的中间值时。 63 | 64 | 总结: 65 | 66 | - LinkedList 插入,删除都是移动指针效率很高。 67 | - 查找需要进行遍历查询,效率较低。 68 | -------------------------------------------------------------------------------- /MD/SQL-optimization.md: -------------------------------------------------------------------------------- 1 | # SQL 优化 2 | 3 | ### 负向查询不能使用索引 4 | 5 | ```sql 6 | select name from user where id not in (1,3,4); 7 | ``` 8 | 应该修改为: 9 | 10 | ``` 11 | select name from user where id in (2,5,6); 12 | ``` 13 | 14 | ### 前导模糊查询不能使用索引 15 | 如: 16 | 17 | ```sql 18 | select name from user where name like '%zhangsan' 19 | ``` 20 | 21 | 非前导则可以: 22 | ```sql 23 | select name from user where name like 'zhangsan%' 24 | ``` 25 | 建议可以考虑使用 `Lucene` 等全文索引工具来代替频繁的模糊查询。 26 | 27 | ### 数据区分不明显的不建议创建索引 28 | 29 | 如 user 表中的性别字段,可以明显区分的才建议创建索引,如身份证等字段。 30 | 31 | ### 字段的默认值不要为 null 32 | 这样会带来和预期不一致的查询结果。 33 | 34 | ### 在字段上进行计算不能命中索引 35 | 36 | ```sql 37 | select name from user where FROM_UNIXTIME(create_time) < CURDATE(); 38 | ``` 39 | 40 | 应该修改为: 41 | 42 | ```sql 43 | select name from user where create_time < FROM_UNIXTIME(CURDATE()); 44 | ``` 45 | 46 | ### 最左前缀问题 47 | 48 | 如果给 user 表中的 username pwd 字段创建了复合索引那么使用以下SQL 都是可以命中索引: 49 | 50 | ```sql 51 | select username from user where username='zhangsan' and pwd ='axsedf1sd' 52 | 53 | select username from user where pwd ='axsedf1sd' and username='zhangsan' 54 | 55 | select username from user where username='zhangsan' 56 | ``` 57 | 58 | 但是使用 59 | 60 | ```sql 61 | select username from user where pwd ='axsedf1sd' 62 | ``` 63 | 是不能命中索引的。 64 | 65 | ### 如果明确知道只有一条记录返回 66 | 67 | ```sql 68 | select name from user where username='zhangsan' limit 1 69 | ``` 70 | 可以提高效率,可以让数据库停止游标移动。 71 | 72 | ### 不要让数据库帮我们做强制类型转换 73 | 74 | ```sql 75 | select name from user where telno=18722222222 76 | ``` 77 | 这样虽然可以查出数据,但是会导致全表扫描。 78 | 79 | 需要修改为 80 | ``` 81 | select name from user where telno='18722222222' 82 | ``` 83 | 84 | ### 如果需要进行 join 的字段两表的字段类型要相同 85 | 86 | 不然也不会命中索引。 -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/HappyNum.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * Function: 判断一个数字是否为快乐数字 19 就是快乐数字 11就不是快乐数字 8 | * 19 9 | * 1*1+9*9=82 10 | * 8*8+2*2=68 11 | * 6*6+8*8=100 12 | * 1*1+0*0+0*0=1 13 | * 14 | * 11 15 | * 1*1+1*1=2 16 | * 2*2=4 17 | * 4*4=16 18 | * 1*1+6*6=37 19 | * 3*3+7*7=58 20 | * 5*5+8*8=89 21 | * 8*8+9*9=145 22 | * 1*1+4*4+5*5=42 23 | * 4*4+2*2=20 24 | * 2*2+0*0=2 25 | * 26 | * 这里结果 1*1+1*1=2 和 2*2+0*0=2 重复,所以不是快乐数字 27 | * @author crossoverJie 28 | * Date: 04/01/2018 14:12 29 | * @since JDK 1.8 30 | */ 31 | public class HappyNum { 32 | 33 | /** 34 | * 判断一个数字是否为快乐数字 35 | * @param number 36 | * @return 37 | */ 38 | public boolean isHappy(int number) { 39 | Set set = new HashSet<>(30); 40 | while (number != 1) { 41 | int sum = 0; 42 | while (number > 0) { 43 | //计算当前值的每位数的平方 相加的和 在放入set中,如果存在相同的就认为不是 happy数字 44 | sum += (number % 10) * (number % 10); 45 | number = number / 10; 46 | } 47 | if (set.contains(sum)) { 48 | return false; 49 | } else { 50 | set.add(sum); 51 | } 52 | number = sum; 53 | } 54 | return true; 55 | } 56 | 57 | 58 | public static void main(String[] args) { 59 | int num = 345; 60 | int i = num % 10; 61 | int i1 = num / 10; 62 | int i2 = i1 / 10; 63 | System.out.println(i); 64 | System.out.println(i1); 65 | System.out.println(i2); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MD/Limiting.md: -------------------------------------------------------------------------------- 1 | # 限流算法 2 | 3 | 限流是解决高并发大流量的一种方案,至少是可以保证应用的可用性。 4 | 5 | 通常有以下两种限流方案: 6 | 7 | - 漏桶算法 8 | - 令牌桶算法 9 | 10 | ## 漏桶算法 11 | 12 | ![漏桶算法,来自网络.png](https://i.loli.net/2017/08/11/598c905caa8cb.png) 13 | 14 | 漏桶算法非常简单,就是将流量放入桶中并按照一定的速率流出。如果流量过大时候并不会提高流出效率,而溢出的流量也只能是抛弃掉了。 15 | 16 | 这种算法很简单,但也非常粗暴,无法应对突发的大流量。 17 | 这时可以考虑令牌桶算法。 18 | 19 | ## 令牌桶算法 20 | ![令牌桶算法-来自网络.gif](https://i.loli.net/2017/08/11/598c91f2a33af.gif) 21 | 22 | 令牌桶算法是按照恒定的速率向桶中放入令牌,每当请求经过时则消耗一个或多个令牌。当桶中的令牌为 0 时,请求则会被阻塞。 23 | 24 | > note: 25 | 令牌桶算法支持先消费后付款,比如一个请求可以获取多个甚至全部的令牌,但是需要后面的请求付费。也就是说后面的请求需要等到桶中的令牌补齐之后才能继续获取。 26 | 27 | 实例: 28 | ```java 29 | @Override 30 | public BaseResponse getUserByFeignBatch(@RequestBody UserReqVO userReqVO) { 31 | //调用远程服务 32 | OrderNoReqVO vo = new OrderNoReqVO() ; 33 | vo.setReqNo(userReqVO.getReqNo()); 34 | 35 | RateLimiter limiter = RateLimiter.create(2.0) ; 36 | //批量调用 37 | for (int i = 0 ;i< 10 ; i++){ 38 | double acquire = limiter.acquire(); 39 | logger.debug("获取令牌成功!,消耗=" + acquire); 40 | BaseResponse orderNo = orderServiceClient.getOrderNo(vo); 41 | logger.debug("远程返回:"+JSON.toJSONString(orderNo)); 42 | } 43 | 44 | UserRes userRes = new UserRes() ; 45 | userRes.setUserId(123); 46 | userRes.setUserName("张三"); 47 | 48 | userRes.setReqNo(userReqVO.getReqNo()); 49 | userRes.setCode(StatusEnum.SUCCESS.getCode()); 50 | userRes.setMessage("成功"); 51 | 52 | return userRes ; 53 | } 54 | ``` 55 | 56 | 57 | 1. [单 JVM 限流](http://crossoverjie.top/2017/08/11/sbc4/) 58 | 2. [分布式限流](http://crossoverjie.top/2018/04/28/sbc/sbc7-Distributed-Limit/) 59 | -------------------------------------------------------------------------------- /MD/Consistent-Hash.md: -------------------------------------------------------------------------------- 1 | # 一致 Hash 算法 2 | 3 | 当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题: 4 | 5 | 如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。 6 | 7 | ## Hash 取模 8 | 随机放置就不说了,会带来很多问题。通常最容易想到的方案就是 `hash 取模`了。 9 | 10 | 可以将传入的 Key 按照 `index = hash(key) % N` 这样来计算出需要存放的节点。其中 hash 函数是一个将字符串转换为正整数的哈希映射方法,N 就是节点的数量。 11 | 12 | 这样可以满足数据的均匀分配,但是这个算法的容错性和扩展性都较差。 13 | 14 | 比如增加或删除了一个节点时,所有的 Key 都需要重新计算,显然这样成本较高,为此需要一个算法满足分布均匀同时也要有良好的容错性和拓展性。 15 | 16 | ## 一致 Hash 算法 17 | 18 | 一致 Hash 算法是将所有的哈希值构成了一个环,其范围在 `0 ~ 2^32-1`。如下图: 19 | 20 | ![](https://ws1.sinaimg.cn/large/006tNc79gy1fn8kbmd4ncj30ad08y3yn.jpg) 21 | 22 | 之后将各个节点散列到这个环上,可以用节点的 IP、hostname 这样的唯一性字段作为 Key 进行 `hash(key)`,散列之后如下: 23 | 24 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fn8kf72uwuj30a40a70t5.jpg) 25 | 26 | 之后需要将数据定位到对应的节点上,使用同样的 `hash 函数` 将 Key 也映射到这个环上。 27 | 28 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fn8kj9kd4oj30ax0aomxq.jpg) 29 | 30 | 这样按照顺时针方向就可以把 k1 定位到 `N1节点`,k2 定位到 `N3节点`,k3 定位到 `N2节点`。 31 | 32 | ### 容错性 33 | 这时假设 N1 宕机了: 34 | 35 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fn8kl9pp06j30a409waaj.jpg) 36 | 37 | 依然根据顺时针方向,k2 和 k3 保持不变,只有 k1 被重新映射到了 N3。这样就很好的保证了容错性,当一个节点宕机时只会影响到少少部分的数据。 38 | 39 | ### 拓展性 40 | 41 | 当新增一个节点时: 42 | 43 | ![](https://ws1.sinaimg.cn/large/006tNc79gy1fn8kp1fc9xj30ca0abt9c.jpg) 44 | 45 | 在 N2 和 N3 之间新增了一个节点 N4 ,这时会发现受印象的数据只有 k3,其余数据也是保持不变,所以这样也很好的保证了拓展性。 46 | 47 | ## 虚拟节点 48 | 到目前为止该算法依然也有点问题: 49 | 50 | 当节点较少时会出现数据分布不均匀的情况: 51 | 52 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1fn8krttekbj30c10a5dg5.jpg) 53 | 54 | 这样会导致大部分数据都在 N1 节点,只有少量的数据在 N2 节点。 55 | 56 | 为了解决这个问题,一致哈希算法引入了虚拟节点。将每一个节点都进行多次 hash,生成多个节点放置在环上称为虚拟节点: 57 | 58 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1fn8ktzuswkj30ae0abdgb.jpg) 59 | 60 | 计算时可以在 IP 后加上编号来生成哈希值。 61 | 62 | 这样只需要在原有的基础上多一步由虚拟节点映射到实际节点的步骤即可让少量节点也能满足均匀性。 -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/algorithm/LinkLoopTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class LinkLoopTest { 7 | 8 | /** 9 | * 无环 10 | * @throws Exception 11 | */ 12 | @Test 13 | public void isLoop() throws Exception { 14 | LinkLoop.Node node3 = new LinkLoop.Node("3"); 15 | LinkLoop.Node node2 = new LinkLoop.Node("2") ; 16 | LinkLoop.Node node1 = new LinkLoop.Node("1") ; 17 | 18 | node1.next = node2 ; 19 | node2.next = node3 ; 20 | 21 | LinkLoop linkLoop = new LinkLoop() ; 22 | boolean loop = linkLoop.isLoop(node1); 23 | Assert.assertEquals(loop,false); 24 | } 25 | 26 | /** 27 | * 有环 28 | * @throws Exception 29 | */ 30 | @Test 31 | public void isLoop2() throws Exception { 32 | LinkLoop.Node node3 = new LinkLoop.Node("3"); 33 | LinkLoop.Node node2 = new LinkLoop.Node("2") ; 34 | LinkLoop.Node node1 = new LinkLoop.Node("1") ; 35 | 36 | node1.next = node2 ; 37 | node2.next = node3 ; 38 | node3.next = node1 ; 39 | 40 | LinkLoop linkLoop = new LinkLoop() ; 41 | boolean loop = linkLoop.isLoop(node1); 42 | Assert.assertEquals(loop,true); 43 | } 44 | 45 | /** 46 | * 无环 47 | * @throws Exception 48 | */ 49 | @Test 50 | public void isLoop3() throws Exception { 51 | LinkLoop.Node node2 = new LinkLoop.Node("2") ; 52 | LinkLoop.Node node1 = new LinkLoop.Node("1") ; 53 | 54 | node1.next = node2 ; 55 | 56 | 57 | LinkLoop linkLoop = new LinkLoop() ; 58 | boolean loop = linkLoop.isLoop(node1); 59 | Assert.assertEquals(loop,false); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /MD/GarbageCollection.md: -------------------------------------------------------------------------------- 1 | # 垃圾回收 2 | 3 | > 垃圾回收主要思考三件事情: 4 | 5 | - 哪种内存需要回收? 6 | - 什么时候回收? 7 | - 怎么回收? 8 | 9 | ## 对象是否存活 10 | 11 | ### 引用计数法 12 | 13 | 这是一种非常简单易理解的回收算法。每当有一个地方引用一个对象的时候则在引用计数器上 +1,当失效的时候就 -1,无论什么时候计数器为 0 的时候则认为该对象死亡可以回收了。 14 | 15 | 这种算法虽然简单高效,但是却无法解决**循环引用**的问题,因此 Java 虚拟机并没有采用这种算法。 16 | 17 | ### 可达性分析算法 18 | 主流的语言其实都是采用可达性分析算法: 19 | 20 | 可达性算法是通过一个称为 `GC Roots` 的对象向下搜索,整个搜索路径就称为引用链,当一个对象到 `GC Roots` 没有任何引用链 `JVM` 就认为该对象是可以被回收的。 21 | 22 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fmwqi5mv1jj30e407kmxm.jpg) 23 | 24 | 如图:Object1、2、3、4 都是存活的对象,而 Object5、6、7都是可回收对象。 25 | 26 | 可以用作 `GC-Roots` 的对象有: 27 | 28 | - 方法区中静态变量所引用的对象。 29 | - 虚拟机栈中所引用的对象。 30 | 31 | ## 垃圾回收算法 32 | 33 | ### 标记-清除算法 34 | 35 | 标记清除算法分为两个步骤,标记和清除。 36 | 首先将需要回收的对象标记起来,然后统一清除。但是存在两个主要的问题: 37 | - 标记和清除的效率都不高。 38 | - 清除之后容易出现不连续内存,当需要分配一个较大内存时就不得不需要进行一次垃圾回收。 39 | 40 | 标记清除过程如下: 41 | 42 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fmz99ai1n3j30fj08qdgc.jpg) 43 | 44 | ### 复制算法 45 | 46 | 复制算法是将内存划分为两块大小相等的区域,每次使用时都只用其中一块区域,当发生垃圾回收时会将存活的对象全部复制到未使用的区域,然后对之前的区域进行全部回收。 47 | 48 | 这样简单高效,而且还不存在标记清除算法中的内存碎片问题,但就是有点浪费内存。 49 | 50 | > 在新生代会使用该算法。 51 | 52 | 新生代中分为一个 `Eden` 区和两个 `Survivor` 区。通常两个区域的比例是 `8:1:1` ,使用时会用到 `Eden` 区和其中一个 `Survivor` 区。当发生回收时则会将还存活的对象从 `Eden` ,`Survivor` 区拷贝到另一个 `Survivor` 区,当该区域内存也不足时则会使用分配担保利用老年代来存放内存。 53 | 54 | 复制算法过程: 55 | 56 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fmzavlf4enj30fj08qt9b.jpg) 57 | 58 | 59 | ### 标记整理算法 60 | 61 | 复制算法如果在存活对象较多时效率明显会降低,特别是在老年代中并没有多余的内存区域可以提供内存担保。 62 | 63 | 所以老年代中使用的时候`分配整理算法`,它的原理和`分配清除算法`类似,只是最后一步的清除改为了将存活对象全部移动到一端,然后再将边界之外的内存全部回收。 64 | 65 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1fmzbq55pfdj30fe08s3yx.jpg) 66 | 67 | ### 分代回收算法 68 | 现代多数的商用 `JVM` 的垃圾收集器都是采用的分代回收算法,和之前所提到的算法并没有新的内容。 69 | 70 | 只是将 Java 堆分为了新生代和老年代。由于新生代中存活对象较少,所以采用**复制算法**,简单高效。 71 | 72 | 而老年代中对象较多,并且没有可以担保的内存区域,所以一般采用**标记清除或者是标记整理算法**。 73 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/concurrent/ThreadState.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.concurrent; 2 | 3 | import com.crossoverjie.classloader.Main; 4 | 5 | /** 6 | * Function: 线程状态测试 7 | * 8 | * @author crossoverJie 9 | * Date: 06/03/2018 22:56 10 | * @since JDK 1.8 11 | */ 12 | public class ThreadState { 13 | 14 | public static void main(String[] args) { 15 | new Thread(new TimeWaiting(),"TimeWaiting").start(); 16 | new Thread(new Waiting(),"Waiting").start(); 17 | new Thread(new Blocked(),"Blocked1").start(); 18 | new Thread(new Blocked(),"Blocked2").start(); 19 | } 20 | 21 | static class TimeWaiting implements Runnable{ 22 | 23 | @Override 24 | public void run() { 25 | while (true){ 26 | try { 27 | Thread.sleep(1000); 28 | } catch (InterruptedException e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | } 33 | } 34 | 35 | static class Waiting implements Runnable{ 36 | 37 | @Override 38 | public void run() { 39 | while (true){ 40 | synchronized (Waiting.class){ 41 | try { 42 | Waiting.class.wait(); 43 | } catch (InterruptedException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | 52 | static class Blocked implements Runnable{ 53 | 54 | @Override 55 | public void run() { 56 | while (true){ 57 | synchronized (Blocked.class){ 58 | try { 59 | Thread.sleep(1000); 60 | } catch (InterruptedException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/algorithm/ReverseNodeTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import org.junit.Test; 4 | import static com.crossoverjie.algorithm.ReverseNode.Node; 5 | 6 | public class ReverseNodeTest { 7 | 8 | @Test 9 | public void reverseNode1() throws Exception { 10 | ReverseNode.Node node4 = new Node<>("4",null) ; 11 | Node node3 = new Node<>("3",node4); 12 | Node node2 = new Node<>("2",node3); 13 | Node node1 = new Node("1",node2) ; 14 | 15 | ReverseNode reverseNode = new ReverseNode() ; 16 | reverseNode.reverseNode1(node1); 17 | } 18 | 19 | @Test 20 | public void reverseNode12() throws Exception { 21 | 22 | Node node1 = new Node("1",null) ; 23 | 24 | ReverseNode reverseNode = new ReverseNode() ; 25 | reverseNode.reverseNode1(node1); 26 | } 27 | 28 | @Test 29 | public void reverseNode13() throws Exception { 30 | 31 | Node node1 = null ; 32 | 33 | ReverseNode reverseNode = new ReverseNode() ; 34 | reverseNode.reverseNode1(node1); 35 | } 36 | 37 | 38 | /** 39 | * 头插法 40 | * @throws Exception 41 | */ 42 | @Test 43 | public void reverseHead21() throws Exception { 44 | Node node4 = new Node<>("4",null) ; 45 | Node node3 = new Node<>("3",node4); 46 | Node node2 = new Node<>("2",node3); 47 | Node node1 = new Node("1",node2) ; 48 | 49 | ReverseNode reverseNode = new ReverseNode() ; 50 | reverseNode.reverseNode(node1); 51 | 52 | } 53 | 54 | 55 | @Test 56 | public void recNodeTest31(){ 57 | Node node4 = new Node<>("4",null) ; 58 | Node node3 = new Node<>("3",node4); 59 | Node node2 = new Node<>("2",node3); 60 | Node node1 = new Node("1",node2) ; 61 | 62 | ReverseNode reverseNode = new ReverseNode() ; 63 | reverseNode.recNode(node1); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /MD/ConcurrentHashMap.md: -------------------------------------------------------------------------------- 1 | # ConcurrentHashMap 实现原理 2 | 3 | 由于 `HashMap` 是一个线程不安全的容器,主要体现在容量大于`总量*负载因子`发生扩容时会出现环形链表从而导致死循环。 4 | 5 | 因此需要支持线程安全的并发容器 `ConcurrentHashMap` 。 6 | 7 | ## 数据结构 8 | ![](https://ws2.sinaimg.cn/large/006tNc79ly1fn2f5pgxinj30dw0730t7.jpg) 9 | 10 | 如图所示,是由 `Segment` 数组、`HashEntry` 数组组成,和 `HashMap` 一样,仍然是数组加链表组成。 11 | 12 | `ConcurrentHashMap` 采用了分段锁技术,其中 `Segment` 继承于 `ReentrantLock`。不会像 `HashTable` 那样不管是 `put` 还是 `get` 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 `CurrencyLevel` (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 `Segment` 时,不会影响到其他的 `Segment`。 13 | 14 | ## get 方法 15 | `ConcurrentHashMap` 的 `get` 方法是非常高效的,因为整个过程都不需要加锁。 16 | 17 | 只需要将 `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))。 18 | 19 | ## put 方法 20 | 21 | 内部 `HashEntry` 类 : 22 | ```java 23 | static final class HashEntry { 24 | final int hash; 25 | final K key; 26 | volatile V value; 27 | volatile HashEntry next; 28 | 29 | HashEntry(int hash, K key, V value, HashEntry next) { 30 | this.hash = hash; 31 | this.key = key; 32 | this.value = value; 33 | this.next = next; 34 | } 35 | } 36 | ``` 37 | 38 | 虽然 HashEntry 中的 value 是用 `volatile` 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。 39 | 40 | 首先也是通过 Key 的 Hash 定位到具体的 Segment,在 put 之前会进行一次扩容校验。这里比 HashMap 要好的一点是:HashMap 是插入元素之后再看是否需要扩容,有可能扩容之后后续就没有插入就浪费了本次扩容(扩容非常消耗性能)。 41 | 42 | 而 ConcurrentHashMap 不一样,它是先将数据插入之后再检查是否需要扩容,之后再做插入。 43 | 44 | ## size 方法 45 | 46 | 每个 `Segment` 都有一个 `volatile` 修饰的全局变量 `count` ,求整个 `ConcurrentHashMap` 的 `size` 时很明显就是将所有的 `count` 累加即可。但是 `volatile` 修饰的变量却不能保证多线程的原子性,所有直接累加很容易出现并发问题。 47 | 48 | 但如果每次调用 `size` 方法将其余的修改操作加锁效率也很低。所以做法是先尝试两次将 `count` 累加,如果容器的 `count` 发生了变化再加锁来统计 `size`。 49 | 50 | 至于 `ConcurrentHashMap` 是如何知道在统计时大小发生了变化呢,每个 `Segment` 都有一个 `modCount` 变量,每当进行一次 `put remove` 等操作,`modCount` 将会 +1。只要 `modCount` 发生了变化就认为容器的大小也在发生变化。 51 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/MergeTwoSortedLists.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | /** 4 | * Function: 合并两个排好序的链表 5 | * 6 | * 每次比较两个链表的头结点,将较小结点放到新的链表,最后将新链表指向剩余的链表 7 | * 8 | * @author crossoverJie 9 | * Date: 07/12/2017 13:58 10 | * @since JDK 1.8 11 | */ 12 | public class MergeTwoSortedLists { 13 | 14 | 15 | /** 16 | * 1. 声明一个头结点 17 | * 2. 将头结点的引用赋值给一个临时结点,也可以叫做下一结点。 18 | * 3. 进行循环比较,每次都将指向值较小的那个结点(较小值的引用赋值给 lastNode )。 19 | * 4. 再去掉较小值链表的头结点,指针后移。 20 | * 5. lastNode 指针也向后移,由于 lastNode 是 head 的引用,这样可以保证最终 head 的值是往后更新的。 21 | * 6. 当其中一个链表的指针移到最后时跳出循环。 22 | * 7. 由于这两个链表已经是排好序的,所以剩下的链表必定是最大的值,只需要将指针指向它即可。 23 | * 8. 由于 head 链表的第一个结点是初始化的0,所以只需要返回 0 的下一个结点即是合并了的链表。 24 | * @param l1 25 | * @param l2 26 | * @return 27 | */ 28 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 29 | ListNode head = new ListNode(0) ; 30 | ListNode lastNode = head ; 31 | 32 | while (l1 != null && l2 != null){ 33 | if (l1.currentVal < l2.currentVal){ 34 | lastNode.next = l1 ; 35 | l1 = l1.next ; 36 | } else { 37 | lastNode.next = l2 ; 38 | l2 = l2.next ; 39 | } 40 | lastNode =lastNode.next ; 41 | } 42 | 43 | if (l1 == null){ 44 | lastNode.next = l2 ; 45 | } 46 | if (l2 == null){ 47 | lastNode.next = l1 ; 48 | } 49 | 50 | return head.next ; 51 | } 52 | 53 | 54 | public static class ListNode { 55 | /** 56 | * 当前值 57 | */ 58 | int currentVal; 59 | 60 | /** 61 | * 下一个节点 62 | */ 63 | ListNode next; 64 | 65 | ListNode(int val) { 66 | currentVal = val; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "ListNode{" + 72 | "currentVal=" + currentVal + 73 | ", next=" + next + 74 | '}'; 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/actual/LRULinkedMapTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.actual; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Map; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class LRULinkedMapTest { 10 | @Test 11 | public void put() throws Exception { 12 | LRULinkedMap map = new LRULinkedMap(3) ; 13 | map.put("1",1); 14 | map.put("2",2); 15 | map.put("3",3); 16 | 17 | for (Map.Entry e : map.getAll()){ 18 | System.out.print(e.getKey() + " : " + e.getValue() + "\t"); 19 | } 20 | 21 | System.out.println(""); 22 | map.put("4",4); 23 | for (Map.Entry e : map.getAll()){ 24 | System.out.print(e.getKey() + " : " + e.getValue() + "\t"); 25 | } 26 | } 27 | 28 | @Test 29 | public void put2() throws Exception { 30 | LRULinkedMap map = new LRULinkedMap(4) ; 31 | map.put("1",1); 32 | map.put("2",2); 33 | map.put("3",3); 34 | map.put("4",4); 35 | 36 | for (Map.Entry e : map.getAll()){ 37 | System.out.print(e.getKey() + " : " + e.getValue() + "\t"); 38 | } 39 | 40 | System.out.println(""); 41 | map.put("5",5); 42 | for (Map.Entry e : map.getAll()){ 43 | System.out.print(e.getKey() + " : " + e.getValue() + "\t"); 44 | } 45 | } 46 | @Test 47 | public void get() throws Exception { 48 | LRULinkedMap map = new LRULinkedMap(4) ; 49 | map.put("1",1); 50 | map.put("2",2); 51 | map.put("3",3); 52 | map.put("4",4); 53 | 54 | for (Map.Entry e : map.getAll()){ 55 | System.out.print(e.getKey() + " : " + e.getValue() + "\t"); 56 | } 57 | 58 | System.out.println(""); 59 | map.get("1") ; 60 | for (Map.Entry e : map.getAll()){ 61 | System.out.print(e.getKey() + " : " + e.getValue() + "\t"); 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /MD/HashMap.md: -------------------------------------------------------------------------------- 1 | # HashMap 底层分析 2 | 3 | > 以下基于 JDK1.7 分析。 4 | 5 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1fn84b0ftj4j30eb0560sv.jpg) 6 | 7 | 如图所示,HashMap 底层是基于数组和链表实现的。其中有两个重要的参数: 8 | 9 | - 容量 10 | - 负载因子 11 | 12 | 容量的默认大小是 16,负载因子是 0.75,当 `HashMap` 的 `size > 16*0.75` 时就会发生扩容(容量和负载因子都可以自由调整)。 13 | 14 | ## put 方法 15 | 首先会将传入的 Key 做 `hash` 运算计算出 hashcode,然后根据数组长度取模计算出在数组中的 index 下标。 16 | 17 | 由于在计算中位运算比取模运算效率高的多,所以 HashMap 规定数组的长度为 `2^n` 。这样用 `2^n - 1` 做位运算与取模效果一致,并且效率还要高出许多。 18 | 19 | 由于数组的长度有限,所以难免会出现不同的 Key 通过运算得到的 index 相同,这种情况可以利用链表来解决,HashMap 会在 `table[index]`处形成环形链表,采用头插法将数据插入到链表中。 20 | 21 | ## get 方法 22 | 23 | get 和 put 类似,也是将传入的 Key 计算出 index ,如果该位置上是一个链表就需要遍历整个链表,通过 `key.equals(k)` 来找到对应的元素。 24 | 25 | ## 遍历方式 26 | 27 | 28 | ```java 29 | Iterator> entryIterator = map.entrySet().iterator(); 30 | while (entryIterator.hasNext()) { 31 | Map.Entry next = entryIterator.next(); 32 | System.out.println("key=" + next.getKey() + " value=" + next.getValue()); 33 | } 34 | ``` 35 | 36 | ```java 37 | Iterator iterator = map.keySet().iterator(); 38 | while (iterator.hasNext()){ 39 | String key = iterator.next(); 40 | System.out.println("key=" + key + " value=" + map.get(key)); 41 | 42 | } 43 | ``` 44 | 45 | ```java 46 | map.forEach((key,value)->{ 47 | System.out.println("key=" + key + " value=" + value); 48 | }); 49 | ``` 50 | 51 | **强烈建议**使用第一种 EntrySet 进行遍历。 52 | 53 | 第一种可以把 key value 同时取出,第二种还得需要通过 key 取一次 value,效率较低, 第三种需要 `JDK1.8` 以上,通过外层遍历 table,内层遍历链表或红黑树。 54 | 55 | 56 | ## notice 57 | 58 | 在并发环境下使用 `HashMap` 容易出现死循环。 59 | 60 | 并发场景发生扩容,调用 `resize()` 方法里的 `rehash()` 时,容易出现环形链表。这样当获取一个不存在的 `key` 时,计算出的 `index` 正好是环形链表的下标时就会出现死循环。 61 | 62 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1fn85u0a0d9j30n20ii0tp.jpg) 63 | 64 | > 所以 HashMap 只能在单线程中使用,并且尽量的预设容量,尽可能的减少扩容。 65 | 66 | 在 `JDK1.8` 中对 `HashMap` 进行了优化: 67 | 当 `hash` 碰撞之后写入链表的长度超过了阈值(默认为8),链表将会转换为**红黑树**。 68 | 69 | 假设 `hash` 冲突非常严重,一个数组后面接了很长的链表,此时重新的时间复杂度就是 `O(n)` 。 70 | 71 | 如果是红黑树,时间复杂度就是 `O(logn)` 。 72 | 73 | 大大提高了查询效率。 74 | 75 | 多线程场景下推荐使用 [ConcurrentHashMap](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ConcurrentHashMap.md)。 76 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/ReverseNode.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Function: 三种方式反向打印单向链表 7 | * 8 | * @author crossoverJie 9 | * Date: 10/02/2018 16:14 10 | * @since JDK 1.8 11 | */ 12 | public class ReverseNode { 13 | 14 | 15 | /** 16 | * 利用栈的先进后出特性 17 | * @param node 18 | */ 19 | public void reverseNode1(Node node){ 20 | 21 | System.out.println("====翻转之前===="); 22 | 23 | Stack stack = new Stack<>() ; 24 | while (node != null){ 25 | 26 | System.out.print(node.value + "===>"); 27 | 28 | stack.push(node) ; 29 | node = node.next ; 30 | } 31 | 32 | System.out.println(""); 33 | 34 | System.out.println("====翻转之后===="); 35 | while (!stack.isEmpty()){ 36 | System.out.print(stack.pop().value + "===>"); 37 | } 38 | 39 | } 40 | 41 | 42 | /** 43 | * 利用头插法插入链表 44 | * @param head 45 | */ 46 | public void reverseNode(Node head) { 47 | if (head == null) { 48 | return ; 49 | } 50 | 51 | //最终翻转之后的 Node 52 | Node node ; 53 | 54 | Node pre = head; 55 | Node cur = head.next; 56 | Node next ; 57 | while(cur != null){ 58 | next = cur.next; 59 | 60 | //链表的头插法 61 | cur.next = pre; 62 | pre = cur; 63 | 64 | cur = next; 65 | } 66 | head.next = null; 67 | node = pre; 68 | 69 | 70 | //遍历新链表 71 | while (node != null){ 72 | System.out.println(node.value); 73 | node = node.next ; 74 | } 75 | 76 | } 77 | 78 | 79 | /** 80 | * 递归 81 | * @param node 82 | */ 83 | public void recNode(Node node){ 84 | 85 | if (node == null){ 86 | return ; 87 | } 88 | 89 | if (node.next != null){ 90 | recNode(node.next) ; 91 | } 92 | System.out.print(node.value+"===>"); 93 | } 94 | 95 | 96 | public static class Node{ 97 | public T value; 98 | public Node next ; 99 | 100 | 101 | public Node(T value, Node next ) { 102 | this.next = next; 103 | this.value = value; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /MD/newObject.md: -------------------------------------------------------------------------------- 1 | # 对象的创建与内存分配 2 | 3 | 4 | ## 创建对象 5 | 6 | 当 `JVM` 收到一个 `new` 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被[加载](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ClassLoad.md)过了,如果没有的话则要进行一次类加载。 7 | 8 | 接着就是分配内存了,通常有两种方式: 9 | 10 | - 指针碰撞 11 | - 空闲列表 12 | 13 | 使用指针碰撞的前提是堆内存是**完全工整**的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可。 14 | 15 | 当堆中已经使用的内存和未使用的内存**互相交错**时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式。虚拟机会维护一个空闲的列表,用于记录哪些内存是可以进行分配的,分配时直接从可用内存中直接分配即可。 16 | 17 | 堆中的内存是否工整是有**垃圾收集器**来决定的,如果带有压缩功能的垃圾收集器就是采用指针碰撞的方式来进行内存分配的。 18 | 19 | 分配内存时也会出现并发问题: 20 | 21 | 这样可以在创建对象的时候使用 `CAS` 这样的乐观锁来保证。 22 | 23 | 也可以将内存分配安排在每个线程独有的空间进行,每个线程首先在堆内存中分配一小块内存,称为本地分配缓存(`TLAB : Thread Local Allocation Buffer`)。 24 | 25 | 分配内存时,只需要在自己的分配缓存中分配即可,由于这个内存区域是线程私有的,所以不会出现并发问题。 26 | 27 | 可以使用 `-XX:+/-UseTLAB` 参数来设定 `JVM` 是否开启 `TLAB` 。 28 | 29 | 内存分配之后需要对该对象进行设置,如对象头。对象头的一些应用可以查看 [Synchronize 关键字原理](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Synchronize.md)。 30 | 31 | ### 对象访问 32 | 33 | 一个对象被创建之后自然是为了使用,在 `Java` 中是通过栈来引用堆内存中的对象来进行操作的。 34 | 35 | 对于我们常用的 `HotSpot` 虚拟机来说,这样引用关系是通过直接指针来关联的。 36 | 37 | 如图: 38 | 39 | ![](https://ws2.sinaimg.cn/large/006tKfTcly1fnkmy0bvu3j30o60heaaq.jpg) 40 | 41 | 这样的好处就是:在 Java 里进行频繁的对象访问可以提升访问速度(相对于使用句柄池来说)。 42 | 43 | ## 内存分配 44 | 45 | 46 | ### Eden 区分配 47 | 简单的来说对象都是在堆内存中分配的,往细一点看则是优先在 `Eden` 区分配。 48 | 49 | 这里就涉及到堆内存的划分了,为了方便垃圾回收,JVM 将堆内存分为新生代和老年代。 50 | 51 | 而新生代中又会划分为 `Eden` 区,`from Survivor、to Survivor` 区。 52 | 53 | 其中 `Eden` 和 `Survivor` 区的比例默认是 `8:1:1`,当然也支持参数调整 `-XX:SurvivorRatio=8`。 54 | 55 | 当在 `Eden` 区分配内存不足时,则会发生 `minorGC` ,由于 `Java` 对象多数是**朝生夕灭**的特性,所以 `minorGC` 通常会比较频繁,效率也比较高。 56 | 57 | 当发生 `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` 区内存不足时,则会使用分配担保策略将对象移动到老年代中。 58 | 59 | 谈到 `minorGC` 时,就不得不提到 `fullGC(majorGC)` ,这是指发生在老年代的 `GC` ,不论是效率还是速度都比 `minorGC` 慢的多,回收时还会发生 `stop the world` 使程序发生停顿,所以应当尽量避免发生 `fullGC` 。 60 | 61 | ### 老年代分配 62 | 63 | 也有一些情况会导致对象直接在老年代分配,比如当分配一个大对象时(大的数组,很长的字符串),由于 `Eden` 区没有足够大的连续空间来分配时,会导致提前触发一次 `GC`,所以尽量别频繁的创建大对象。 64 | 65 | 因此 `JVM` 会根据一个阈值来判断大于该阈值对象直接分配到老年代,这样可以避免在新生代频繁的发生 `GC`。 66 | 67 | 68 | 对于一些在新生代的老对象 `JVM` 也会根据某种机制移动到老年代中。 69 | 70 | JVM 是根据记录对象年龄的方式来判断该对象是否应该移动到老年代,根据新生代的复制算法,当一个对象被移动到 `Survivor` 区之后 JVM 就给该对象的年龄记为1,每当熬过一次 `minorGC` 后对象的年龄就 +1 ,直到达到阈值(默认为15)就移动到老年代中。 71 | 72 | > 可以使用 `-XX:MaxTenuringThreshold=15` 来配置这个阈值。 73 | 74 | 75 | ## 总结 76 | 77 | 虽说这些内容略显枯燥,但当应用发生不正常的 `GC` 时,可以方便更快的定位问题。 -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/algorithm/BinaryNode.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * Function: 7 | * 8 | * @author crossoverJie 9 | * Date: 04/01/2018 18:26 10 | * @since JDK 1.8 11 | */ 12 | public class BinaryNode { 13 | private Object data ; 14 | private BinaryNode left ; 15 | private BinaryNode right ; 16 | 17 | public BinaryNode() { 18 | } 19 | 20 | public BinaryNode(Object data, BinaryNode left, BinaryNode right) { 21 | this.data = data; 22 | this.left = left; 23 | this.right = right; 24 | } 25 | 26 | public Object getData() { 27 | return data; 28 | } 29 | 30 | public void setData(Object data) { 31 | this.data = data; 32 | } 33 | 34 | public BinaryNode getLeft() { 35 | return left; 36 | } 37 | 38 | public void setLeft(BinaryNode left) { 39 | this.left = left; 40 | } 41 | 42 | public BinaryNode getRight() { 43 | return right; 44 | } 45 | 46 | public void setRight(BinaryNode right) { 47 | this.right = right; 48 | } 49 | 50 | 51 | public BinaryNode createNode(){ 52 | BinaryNode node = new BinaryNode("1",null,null) ; 53 | BinaryNode left2 = new BinaryNode("2",null,null) ; 54 | BinaryNode left3 = new BinaryNode("3",null,null) ; 55 | BinaryNode left4 = new BinaryNode("4",null,null) ; 56 | BinaryNode left5 = new BinaryNode("5",null,null) ; 57 | BinaryNode left6 = new BinaryNode("6",null,null) ; 58 | node.setLeft(left2) ; 59 | left2.setLeft(left4); 60 | left2.setRight(left6); 61 | node.setRight(left3); 62 | left3.setRight(left5) ; 63 | return node ; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "BinaryNode{" + 69 | "data=" + data + 70 | ", left=" + left + 71 | ", right=" + right + 72 | '}'; 73 | } 74 | 75 | 76 | /** 77 | * 二叉树的层序遍历 借助于队列来实现 借助队列的先进先出的特性 78 | * 79 | * 首先将根节点入队列 然后遍历队列。 80 | * 首先将根节点打印出来,接着判断左节点是否为空 不为空则加入队列 81 | * @param node 82 | */ 83 | public void levelIterator(BinaryNode node){ 84 | LinkedList queue = new LinkedList<>() ; 85 | 86 | //先将根节点入队 87 | queue.offer(node) ; 88 | BinaryNode current ; 89 | while (!queue.isEmpty()){ 90 | current = queue.poll(); 91 | 92 | System.out.print(current.data+"--->"); 93 | 94 | if (current.getLeft() != null){ 95 | queue.offer(current.getLeft()) ; 96 | } 97 | if (current.getRight() != null){ 98 | queue.offer(current.getRight()) ; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/guava/CacheLoaderTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.guava; 2 | 3 | import com.google.common.cache.CacheBuilder; 4 | import com.google.common.cache.CacheLoader; 5 | import com.google.common.cache.LoadingCache; 6 | import com.google.common.collect.Interner; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.LinkedBlockingQueue; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.atomic.AtomicLong; 14 | 15 | /** 16 | * Function: 17 | * 18 | * @author crossoverJie 19 | * Date: 2018/6/12 15:33 20 | * @since JDK 1.8 21 | */ 22 | public class CacheLoaderTest { 23 | private final static Logger LOGGER = LoggerFactory.getLogger(CacheLoaderTest.class); 24 | private LoadingCache loadingCache ; 25 | private final static Integer KEY = 1000; 26 | 27 | 28 | private final static LinkedBlockingQueue QUEUE = new LinkedBlockingQueue<>(1000); 29 | 30 | 31 | private void init() throws InterruptedException { 32 | loadingCache = CacheBuilder.newBuilder() 33 | .expireAfterWrite(2, TimeUnit.SECONDS) 34 | 35 | .build(new CacheLoader() { 36 | @Override 37 | public AtomicLong load(Integer key) throws Exception { 38 | return new AtomicLong(0); 39 | } 40 | }); 41 | 42 | 43 | for (int i = 10; i < 15; i++) { 44 | QUEUE.put(i); 45 | } 46 | } 47 | 48 | private void checkAlert(Integer integer) { 49 | try { 50 | 51 | //loadingCache.put(integer,new AtomicLong(integer)); 52 | 53 | TimeUnit.SECONDS.sleep(5); 54 | 55 | 56 | LOGGER.info("当前缓存值={},缓存大小={}", loadingCache.get(KEY),loadingCache.size()); 57 | LOGGER.info("缓存的所有内容={}",loadingCache.asMap().toString()); 58 | loadingCache.get(KEY).incrementAndGet(); 59 | 60 | } catch (ExecutionException e ) { 61 | LOGGER.error("Exception", e); 62 | } catch (InterruptedException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | public static void main(String[] args) throws InterruptedException { 67 | CacheLoaderTest cacheLoaderTest = new CacheLoaderTest() ; 68 | cacheLoaderTest.init(); 69 | 70 | 71 | 72 | while (true) { 73 | 74 | try { 75 | Integer integer = QUEUE.poll(200, TimeUnit.MILLISECONDS); 76 | if (null == integer) { 77 | break; 78 | } 79 | //TimeUnit.SECONDS.sleep(5); 80 | cacheLoaderTest.checkAlert(integer); 81 | LOGGER.info("job running times={}", integer); 82 | } catch (InterruptedException e) { 83 | e.printStackTrace(); 84 | } 85 | } 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/algorithm/MergeTwoSortedListsTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.algorithm; 2 | 3 | import com.crossoverjie.algorithm.MergeTwoSortedLists.ListNode; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | public class MergeTwoSortedListsTest { 9 | MergeTwoSortedLists mergeTwoSortedLists ; 10 | @Before 11 | public void setUp() throws Exception { 12 | mergeTwoSortedLists = new MergeTwoSortedLists(); 13 | } 14 | 15 | @Test 16 | public void mergeTwoLists() throws Exception { 17 | ListNode l1 = new ListNode(1) ; 18 | ListNode l1_2 = new ListNode(4); 19 | l1.next = l1_2 ; 20 | ListNode l1_3 = new ListNode(5) ; 21 | l1_2.next = l1_3 ; 22 | 23 | ListNode l2 = new ListNode(1) ; 24 | ListNode l2_2 = new ListNode(3) ; 25 | l2.next = l2_2 ; 26 | ListNode l2_3 = new ListNode(6) ; 27 | l2_2.next = l2_3 ; 28 | ListNode l2_4 = new ListNode(9) ; 29 | l2_3.next = l2_4 ; 30 | ListNode listNode = mergeTwoSortedLists.mergeTwoLists(l1, l2); 31 | 32 | 33 | ListNode node1 = new ListNode(1) ; 34 | ListNode node2 = new ListNode(1); 35 | node1.next = node2; 36 | ListNode node3 = new ListNode(3) ; 37 | node2.next= node3 ; 38 | ListNode node4 = new ListNode(4) ; 39 | node3.next = node4 ; 40 | ListNode node5 = new ListNode(5) ; 41 | node4.next = node5 ; 42 | ListNode node6 = new ListNode(6) ; 43 | node5.next = node6 ; 44 | ListNode node7 = new ListNode(9) ; 45 | node6.next = node7 ; 46 | Assert.assertEquals(node1.toString(),listNode.toString()); 47 | 48 | 49 | } 50 | 51 | @Test 52 | public void mergeTwoLists2() throws Exception { 53 | 54 | ListNode l2 = new ListNode(1) ; 55 | ListNode l2_2 = new ListNode(3) ; 56 | l2.next = l2_2 ; 57 | ListNode l2_3 = new ListNode(6) ; 58 | l2_2.next = l2_3 ; 59 | ListNode l2_4 = new ListNode(9) ; 60 | l2_3.next = l2_4 ; 61 | ListNode listNode = mergeTwoSortedLists.mergeTwoLists(null, l2); 62 | 63 | System.out.println(listNode.toString()); 64 | 65 | 66 | } 67 | 68 | @Test 69 | public void mergeTwoLists3() throws Exception { 70 | 71 | ListNode l2 = new ListNode(1) ; 72 | ListNode l2_2 = new ListNode(3) ; 73 | l2.next = l2_2 ; 74 | ListNode l2_3 = new ListNode(6) ; 75 | l2_2.next = l2_3 ; 76 | ListNode l2_4 = new ListNode(9) ; 77 | l2_3.next = l2_4 ; 78 | ListNode listNode = mergeTwoSortedLists.mergeTwoLists(l2, null); 79 | 80 | 81 | ListNode node1 = new ListNode(1) ; 82 | ListNode node2 = new ListNode(3); 83 | node1.next = node2; 84 | ListNode node3 = new ListNode(6) ; 85 | node2.next= node3 ; 86 | ListNode node4 = new ListNode(9) ; 87 | node3.next = node4 ; 88 | 89 | Assert.assertEquals(node1.toString(),listNode.toString()); 90 | 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/actual/TwoThreadWaitNotify.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.actual; 2 | 3 | /** 4 | * Function:两个线程交替执行打印 1~100 5 | * 等待通知机制版 6 | * 7 | * @author crossoverJie 8 | * Date: 07/03/2018 13:19 9 | * @since JDK 1.8 10 | */ 11 | public class TwoThreadWaitNotify { 12 | 13 | private int start = 1; 14 | 15 | private boolean flag = false; 16 | 17 | public static void main(String[] args) { 18 | TwoThreadWaitNotify twoThread = new TwoThreadWaitNotify(); 19 | 20 | Thread t1 = new Thread(new OuNum(twoThread)); 21 | t1.setName("t1"); 22 | 23 | 24 | Thread t2 = new Thread(new JiNum(twoThread)); 25 | t2.setName("t2"); 26 | 27 | t1.start(); 28 | t2.start(); 29 | } 30 | 31 | /** 32 | * 偶数线程 33 | */ 34 | public static class OuNum implements Runnable { 35 | private TwoThreadWaitNotify number; 36 | 37 | public OuNum(TwoThreadWaitNotify number) { 38 | this.number = number; 39 | } 40 | 41 | @Override 42 | public void run() { 43 | 44 | while (number.start <= 100) { 45 | synchronized (TwoThreadWaitNotify.class) { 46 | System.out.println("偶数线程抢到锁了"); 47 | if (number.flag) { 48 | System.out.println(Thread.currentThread().getName() + "+-+偶数" + number.start); 49 | number.start++; 50 | 51 | number.flag = false; 52 | TwoThreadWaitNotify.class.notify(); 53 | 54 | }else { 55 | try { 56 | TwoThreadWaitNotify.class.wait(); 57 | } catch (InterruptedException e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | } 62 | 63 | } 64 | } 65 | } 66 | 67 | 68 | /** 69 | * 奇数线程 70 | */ 71 | public static class JiNum implements Runnable { 72 | private TwoThreadWaitNotify number; 73 | 74 | public JiNum(TwoThreadWaitNotify number) { 75 | this.number = number; 76 | } 77 | 78 | @Override 79 | public void run() { 80 | while (number.start <= 100) { 81 | synchronized (TwoThreadWaitNotify.class) { 82 | System.out.println("奇数线程抢到锁了"); 83 | if (!number.flag) { 84 | System.out.println(Thread.currentThread().getName() + "+-+奇数" + number.start); 85 | number.start++; 86 | 87 | number.flag = true; 88 | 89 | TwoThreadWaitNotify.class.notify(); 90 | }else { 91 | try { 92 | TwoThreadWaitNotify.class.wait(); 93 | } catch (InterruptedException e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/actual/TwoThread.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.actual; 2 | 3 | import java.util.concurrent.locks.Lock; 4 | import java.util.concurrent.locks.ReentrantLock; 5 | 6 | /** 7 | * Function: 两个线程交替执行打印 1~100 8 | * 9 | * lock 版 10 | * 11 | * @author crossoverJie 12 | * Date: 11/02/2018 10:04 13 | * @since JDK 1.8 14 | */ 15 | public class TwoThread { 16 | 17 | private int start = 1; 18 | 19 | /** 20 | * 保证内存可见性 21 | * 其实用锁了之后也可以保证可见性 这里用不用 volatile 都一样 22 | */ 23 | private boolean flag = false; 24 | 25 | /** 26 | * 重入锁 27 | */ 28 | private final static Lock LOCK = new ReentrantLock(); 29 | 30 | public static void main(String[] args) { 31 | TwoThread twoThread = new TwoThread(); 32 | 33 | Thread t1 = new Thread(new OuNum(twoThread)); 34 | t1.setName("t1"); 35 | 36 | 37 | Thread t2 = new Thread(new JiNum(twoThread)); 38 | t2.setName("t2"); 39 | 40 | t1.start(); 41 | t2.start(); 42 | } 43 | 44 | /** 45 | * 偶数线程 46 | */ 47 | public static class OuNum implements Runnable { 48 | 49 | private TwoThread number; 50 | 51 | public OuNum(TwoThread number) { 52 | this.number = number; 53 | } 54 | 55 | @Override 56 | public void run() { 57 | while (number.start <= 100) { 58 | 59 | if (number.flag) { 60 | try { 61 | LOCK.lock(); 62 | System.out.println(Thread.currentThread().getName() + "+-+" + number.start); 63 | number.start++; 64 | number.flag = false; 65 | 66 | 67 | } finally { 68 | LOCK.unlock(); 69 | } 70 | } else { 71 | try { 72 | //防止线程空转 73 | Thread.sleep(10); 74 | } catch (InterruptedException e) { 75 | e.printStackTrace(); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * 奇数线程 84 | */ 85 | public static class JiNum implements Runnable { 86 | 87 | private TwoThread number; 88 | 89 | public JiNum(TwoThread number) { 90 | this.number = number; 91 | } 92 | 93 | @Override 94 | public void run() { 95 | while (number.start <= 100) { 96 | 97 | if (!number.flag) { 98 | try { 99 | LOCK.lock(); 100 | System.out.println(Thread.currentThread().getName() + "+-+" + number.start); 101 | number.start++; 102 | number.flag = true; 103 | 104 | 105 | } finally { 106 | LOCK.unlock(); 107 | } 108 | } else { 109 | try { 110 | //防止线程空转 111 | Thread.sleep(10); 112 | } catch (InterruptedException e) { 113 | e.printStackTrace(); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/actual/ReadFile.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.actual; 2 | 3 | import com.google.common.io.Files; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.charset.Charset; 10 | import java.util.*; 11 | 12 | /** 13 | * Function:读取文件 14 | * 15 | * @author crossoverJie 16 | * Date: 05/01/2018 14:11 17 | * @since JDK 1.8 18 | */ 19 | public class ReadFile { 20 | private final static Logger LOGGER = LoggerFactory.getLogger(ReadFile.class); 21 | 22 | private static List content ; 23 | private static String path ="/Users/chenjie/Desktop/test.log" ; 24 | 25 | /** 26 | * 查找关键字 27 | */ 28 | private static final String KEYWORD = "login" ; 29 | 30 | 31 | private static Map countMap ; 32 | /** 33 | * 去重集合 34 | */ 35 | private static Set contentSet ; 36 | public static void main(String[] args) { 37 | contentSet = new TreeSet<>() ; 38 | countMap = new HashMap<>(30) ; 39 | File file = new File(path) ; 40 | try { 41 | //查找 42 | sortAndFindKeyWords(file); 43 | 44 | LOGGER.info(contentSet.toString()); 45 | 46 | } catch (IOException e) { 47 | LOGGER.error("IOException",e); 48 | } 49 | } 50 | 51 | /** 52 | * 查找关键字 53 | * @param file 54 | * @throws IOException 55 | */ 56 | private static void sortAndFindKeyWords(File file) throws IOException { 57 | content = Files.readLines(file, Charset.defaultCharset()); 58 | LOGGER.info(String.valueOf(content)); 59 | 60 | for (String value : content) { 61 | 62 | boolean flag = value.contains(KEYWORD) ; 63 | if (!flag){ 64 | continue; 65 | } 66 | 67 | if (countMap.containsKey(value)){ 68 | countMap.put(value,countMap.get(value) + 1) ; 69 | } else { 70 | countMap.put(value,1) ; 71 | } 72 | } 73 | 74 | for (String key :countMap.keySet()){ 75 | SortString sort = new SortString() ; 76 | sort.setKey(key); 77 | sort.setCount(countMap.get(key)); 78 | 79 | contentSet.add(sort) ; 80 | } 81 | 82 | } 83 | 84 | private static class SortString implements Comparable{ 85 | 86 | private String key ; 87 | private Integer count ; 88 | 89 | public String getKey() { 90 | return key; 91 | } 92 | 93 | public void setKey(String key) { 94 | this.key = key; 95 | } 96 | 97 | public Integer getCount() { 98 | return count; 99 | } 100 | 101 | public void setCount(Integer count) { 102 | this.count = count; 103 | } 104 | 105 | 106 | @Override 107 | public int compareTo(SortString o) { 108 | if (this.getCount() >o.getCount()){ 109 | return 1; 110 | }else { 111 | return -1 ; 112 | } 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | return "SortString{" + 118 | "key='" + key + '\'' + 119 | ", count=" + count + 120 | '}'; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /MD/Synchronize.md: -------------------------------------------------------------------------------- 1 | # Synchronize 关键字原理 2 | 3 | 众所周知 `Synchronize` 关键字是解决并发问题常用解决方案,有以下三种使用方式: 4 | 5 | - 同步普通方法,锁的是当前对象。 6 | - 同步静态方法,锁的是当前 `Class` 对象。 7 | - 同步块,锁的是 `{}` 中的对象。 8 | 9 | 10 | 实现原理: 11 | `JVM` 是通过进入、退出对象监视器( `Monitor` )来实现对方法、同步块的同步的。 12 | 13 | 具体实现是在编译之后在同步方法调用前加入一个 `monitor.enter` 指令,在退出方法和异常处插入 `monitor.exit` 的指令。 14 | 15 | 其本质就是对一个对象监视器( `Monitor` )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。 16 | 17 | 而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 `monitor.exit` 之后才能尝试继续获取锁。 18 | 19 | 流程图如下: 20 | 21 | ![](https://ws2.sinaimg.cn/large/006tNc79ly1fn27fkl07jj31e80hyn0n.jpg) 22 | 23 | 24 | 通过一段代码来演示: 25 | 26 | ```java 27 | public static void main(String[] args) { 28 | synchronized (Synchronize.class){ 29 | System.out.println("Synchronize"); 30 | } 31 | } 32 | ``` 33 | 34 | 使用 `javap -c Synchronize` 可以查看编译之后的具体信息。 35 | 36 | ``` 37 | public class com.crossoverjie.synchronize.Synchronize { 38 | public com.crossoverjie.synchronize.Synchronize(); 39 | Code: 40 | 0: aload_0 41 | 1: invokespecial #1 // Method java/lang/Object."":()V 42 | 4: return 43 | 44 | public static void main(java.lang.String[]); 45 | Code: 46 | 0: ldc #2 // class com/crossoverjie/synchronize/Synchronize 47 | 2: dup 48 | 3: astore_1 49 | **4: monitorenter** 50 | 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 51 | 8: ldc #4 // String Synchronize 52 | 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 53 | 13: aload_1 54 | **14: monitorexit** 55 | 15: goto 23 56 | 18: astore_2 57 | 19: aload_1 58 | 20: monitorexit 59 | 21: aload_2 60 | 22: athrow 61 | 23: return 62 | Exception table: 63 | from to target type 64 | 5 15 18 any 65 | 18 21 18 any 66 | } 67 | ``` 68 | 69 | 可以看到在同步块的入口和出口分别有 `monitorenter,monitorexit` 70 | 指令。 71 | 72 | 73 | ## 锁优化 74 | `synchronize` 很多都称之为重量锁,`JDK1.6` 中对 `synchronize` 进行了各种优化,为了能减少获取和释放锁带来的消耗引入了`偏向锁`和`轻量锁`。 75 | 76 | 77 | ### 轻量锁 78 | 当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(`Lock Record`)区域,同时将锁对象的对象头中 `Mark Word` 拷贝到锁记录中,再尝试使用 `CAS` 将 `Mark Word` 更新为指向锁记录的指针。 79 | 80 | 如果更新**成功**,当前线程就获得了锁。 81 | 82 | 如果更新**失败** `JVM` 会先检查锁对象的 `Mark Word` 是否指向当前线程的锁记录。 83 | 84 | 如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。 85 | 86 | 不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,**轻量锁就会膨胀为重量锁**。 87 | 88 | #### 解锁 89 | 轻量锁的解锁过程也是利用 `CAS` 来实现的,会尝试锁记录替换回锁对象的 `Mark Word` 。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为`重量锁`) 90 | 91 | 轻量锁能提升性能的原因是: 92 | 93 | 认为大多数锁在整个同步周期都不存在竞争,所以使用 `CAS` 比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 `CAS` 的开销,甚至比重量锁更慢。 94 | 95 | ### 偏向锁 96 | 97 | 为了进一步的降低获取锁的代价,`JDK1.6` 之后还引入了偏向锁。 98 | 99 | 偏向锁的特征是:锁不存在多线程竞争,并且应由一个线程多次获得锁。 100 | 101 | 当线程访问同步块时,会使用 `CAS` 将线程 ID 更新到锁对象的 `Mark Word` 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。 102 | 103 | #### 释放锁 104 | 当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 `Mark Word` 设置为无锁或者是轻量锁状态。 105 | 106 | 偏向锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用 `-XX:-userBiasedLocking=false` 来关闭偏向锁,并默认进入轻量锁。 107 | 108 | 109 | ### 其他优化 110 | 111 | #### 适应性自旋 112 | 在使用 `CAS` 时,如果操作失败,`CAS` 会自旋再次尝试。由于自旋是需要消耗 `CPU` 资源的,所以如果长期自旋就白白浪费了 `CPU`。`JDK1.6`加入了适应性自旋: 113 | 114 | > 如果某个锁自旋很少成功获得,那么下一次就会减少自旋。 115 | 116 | -------------------------------------------------------------------------------- /MD/OOM-analysis.md: -------------------------------------------------------------------------------- 1 | # OOM 分析 2 | 3 | ## Java 堆内存溢出 4 | 5 | 在 Java 堆中只要不断的创建对象,并且 `GC-Roots` 到对象之间存在引用链,这样 `JVM` 就不会回收对象。 6 | 7 | 只要将`-Xms(最小堆)`,`-Xmx(最大堆)` 设置为一样禁止自动扩展堆内存。 8 | 9 | 10 | 当使用一个 `while(true)` 循环来不断创建对象就会发生 `OutOfMemory`,还可以使用 `-XX:+HeapDumpOutofMemoryErorr` 当发生 OOM 时会自动 dump 堆栈到文件中。 11 | 12 | 伪代码: 13 | 14 | ```java 15 | public static void main(String[] args) { 16 | List list = new ArrayList<>(10) ; 17 | while (true){ 18 | list.add("1") ; 19 | } 20 | } 21 | ``` 22 | 23 | 当出现 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` 是如何进行关联的,是否存在对象的生命周期过长,或者是这些对象确实改存在的,那就要考虑将堆内存调大了。 24 | 25 | ``` 26 | Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 27 | at java.util.Arrays.copyOf(Arrays.java:3210) 28 | at java.util.Arrays.copyOf(Arrays.java:3181) 29 | at java.util.ArrayList.grow(ArrayList.java:261) 30 | at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) 31 | at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) 32 | at java.util.ArrayList.add(ArrayList.java:458) 33 | at com.crossoverjie.oom.HeapOOM.main(HeapOOM.java:18) 34 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 35 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 36 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 37 | at java.lang.reflect.Method.invoke(Method.java:498) 38 | at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) 39 | 40 | Process finished with exit code 1 41 | 42 | ``` 43 | `java.lang.OutOfMemoryError: Java heap space`表示堆内存溢出。 44 | 45 | 46 | ## MetaSpace (元数据) 内存溢出 47 | 48 | > `JDK8` 中将永久代移除,使用 `MetaSpace` 来保存类加载之后的类信息,字符串常量池也被移动到 Java 堆。 49 | 50 | `PermSize` 和 `MaxPermSize` 已经不能使用了,在 JDK8 中配置这两个参数将会发出警告。 51 | 52 | 53 | JDK 8 中将类信息移到到了本地堆内存(Native Heap)中,将原有的永久代移动到了本地堆中成为 `MetaSpace` ,如果不指定该区域的大小,JVM 将会动态的调整。 54 | 55 | 可以使用 `-XX:MaxMetaspaceSize=10M` 来限制最大元数据。这样当不停的创建类时将会占满该区域并出现 `OOM`。 56 | 57 | ```java 58 | public static void main(String[] args) { 59 | while (true){ 60 | Enhancer enhancer = new Enhancer() ; 61 | enhancer.setSuperclass(HeapOOM.class); 62 | enhancer.setUseCache(false) ; 63 | enhancer.setCallback(new MethodInterceptor() { 64 | @Override 65 | public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 66 | return methodProxy.invoke(o,objects) ; 67 | } 68 | }); 69 | enhancer.create() ; 70 | 71 | } 72 | } 73 | ``` 74 | 使用 `cglib` 不停的创建新类,最终会抛出: 75 | ``` 76 | Caused by: java.lang.reflect.InvocationTargetException 77 | at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) 78 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 79 | at java.lang.reflect.Method.invoke(Method.java:498) 80 | at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459) 81 | at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336) 82 | ... 11 more 83 | Caused by: java.lang.OutOfMemoryError: Metaspace 84 | at java.lang.ClassLoader.defineClass1(Native Method) 85 | at java.lang.ClassLoader.defineClass(ClassLoader.java:763) 86 | ... 16 more 87 | ``` 88 | 89 | 注意:这里的 OOM 伴随的是 `java.lang.OutOfMemoryError: Metaspace` 也就是元数据溢出。 90 | 91 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | DEBUG 15 | ACCEPT 16 | DENY 17 | 18 | ${LOG_PATH}/${LOG_FILE_NAME}-debug.log 19 | 21 | 22 | ${LOG_PATH}/${LOG_FILE_NAME}_debug_%d{yyyy-MM-dd}.log.%d{yyyy-MM-dd} 23 | 24 | 25 | [%date{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %p %C %t, - %msg%n 26 | UTF-8 27 | 28 | 29 | 30 | 31 | 32 | INFO 33 | ACCEPT 34 | DENY 35 | 36 | ${LOG_PATH}/${LOG_FILE_NAME}-info.log 37 | 39 | ${LOG_PATH}/${LOG_FILE_NAME}_info_%d{yyyy-MM-dd}.log.%d{yyyy-MM-dd} 40 | 41 | 42 | 43 | [%date{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %p %C %t, - %msg%n 44 | UTF-8 45 | 46 | 47 | 48 | 49 | 50 | ERROR 51 | ACCEPT 52 | DENY 53 | 54 | ${LOG_PATH}/${LOG_FILE_NAME}-error.log 55 | 56 | ${LOG_PATH}/${LOG_FILE_NAME}_error_%d{yyyy-MM-dd}.%d{yyyy-MM-dd} 57 | 58 | 59 | [%date{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %p %C %t, - %msg%n 60 | UTF-8 61 | 62 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /MD/Threadcore.md: -------------------------------------------------------------------------------- 1 | # Java 多线程三大核心 2 | 3 | ## 原子性 4 | `Java` 的原子性就和数据库事物的原子性差不多,一个操作中要么全部执行成功或者失败。 5 | 6 | `JMM` 只是保证了基本的原子性,但类似于 `i++` 之类的操作,看似是原子操作,其实里面涉及到: 7 | 8 | - 获取 i 的值。 9 | - 自增。 10 | - 再赋值给 i。 11 | 12 | 这三步操作,所以想要实现 `i++` 这样的原子操作就需要用到 `synchronize` 或者是 `lock` 进行加锁处理。 13 | 14 | 如果是基础类的自增操作可以使用 `AtomicInteger` 这样的原子类来实现(其本质是利用了 `CPU` 级别的 的 `CAS` 指令来完成的)。 15 | 16 | 其中用的最多的方法就是: `incrementAndGet()` 以原子的方式自增。 17 | 源码如下: 18 | 19 | ```java 20 | public final long incrementAndGet() { 21 | for (;;) { 22 | long current = get(); 23 | long next = current + 1; 24 | if (compareAndSet(current, next)) 25 | return next; 26 | } 27 | } 28 | ``` 29 | 30 | 首先是获得当前的值,然后自增 +1。接着则是最核心的 `compareAndSet() ` 来进行原子更新。 31 | 32 | ```java 33 | public final boolean compareAndSet(long expect, long update) { 34 | return unsafe.compareAndSwapLong(this, valueOffset, expect, update); 35 | } 36 | ``` 37 | 38 | 其逻辑就是判断当前的值是否被更新过,是否等于 `current`,如果等于就说明没有更新过然后将当前的值更新为 `next`,如果不等于则返回`false` 进入循环,直到更新成功为止。 39 | 40 | 还有其中的 `get()` 方法也很关键,返回的是当前的值,当前值用了 `volatile` 关键词修饰,保证了内存可见性。 41 | 42 | ```java 43 | private volatile int value; 44 | ``` 45 | 46 | 47 | ## 可见性 48 | 49 | 现代计算机中,由于 `CPU` 直接从主内存中读取数据的效率不高,所以都会对应的 `CPU` 高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。 50 | 51 | ![](https://ws2.sinaimg.cn/large/006tKfTcly1fmouu3fpokj31ae0osjt1.jpg) 52 | 53 | 如上图所示。 54 | 55 | `volatile` 关键字就是用于保证内存可见性,当线程A更新了 volatile 修饰的变量时,它会立即刷新到主线程,并且将其余缓存中该变量的值清空,导致其余线程只能去主内存读取最新值。 56 | 57 | 使用 `volatile` 关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。 58 | 59 | `synchronize`和加锁也能能保证可见性,实现原理就是在释放锁之前其余线程是访问不到这个共享变量的。但是和 `volatile` 相比开销较大。 60 | 61 | ## 顺序性 62 | 以下这段代码: 63 | 64 | ```java 65 | int a = 100 ; //1 66 | int b = 200 ; //2 67 | int c = a + b ; //3 68 | ``` 69 | 70 | 正常情况下的执行顺序应该是 `1>>2>>3`。但是有时 `JVM` 为了提高整体的效率会进行指令重排导致执行的顺序可能是 `2>>1>>3`。但是 `JVM` 也不能是什么都进行重排,是在保证最终结果和代码顺序执行结果一致的情况下才可能进行重排。 71 | 72 | 重排在单线程中不会出现问题,但在多线程中会出现数据不一致的问题。 73 | 74 | Java 中可以使用 `volatile` 来保证顺序性,`synchronize 和 lock` 也可以来保证有序性,和保证原子性的方式一样,通过同一段时间只能一个线程访问来实现的。 75 | 76 | 除了通过 `volatile` 关键字显式的保证顺序之外, `JVM` 还通过 `happen-before` 原则来隐式的保证顺序性。 77 | 78 | 其中有一条就是适用于 `volatile` 关键字的,针对于 `volatile` 关键字的写操作肯定是在读操作之前,也就是说读取的值肯定是最新的。 79 | 80 | ### volatile 的应用 81 | 82 | #### 双重检查锁的单例模式 83 | 84 | 可以用 `volatile` 实现一个双重检查锁的单例模式: 85 | 86 | ```java 87 | public class Singleton{ 88 | private static volatile Singleton singleton ; 89 | private Singleton(){} 90 | public static Singleton getInstance(){ 91 | if(singleton == null){ 92 | synchronize(Singleton.class){ 93 | if(singleton == null){ 94 | singleton = new Singleton(); 95 | } 96 | } 97 | } 98 | return singleton ; 99 | } 100 | 101 | } 102 | ``` 103 | 104 | 这里的 `volatile` 关键字主要是为了防止指令重排。 105 | 如果不用 `volatile` ,`singleton = new Singleton();`,这段代码其实是分为三步: 106 | 107 | - 分配内存空间。(1) 108 | - 初始化对象。(2) 109 | - 将 `singleton` 对象指向分配的内存地址。(3) 110 | 111 | 加上 `volatile` 是为了让以上的三步操作顺序执行,反之有可能第二步在第三步之前被执行就有可能某个线程拿到的单例对象是还没有初始化的,以致于报错。 112 | 113 | #### 控制停止线程的标记 114 | 115 | ```java 116 | private volatile boolean flag ; 117 | private void run(){ 118 | new Thread(new Runnable(){ 119 | if(flag){ 120 | doSomeThing(); 121 | } 122 | }); 123 | } 124 | 125 | private void stop(){ 126 | flag = false ; 127 | } 128 | ``` 129 | 130 | 这里如果没有用 volatile 来修饰 flag ,就有可能其中一个线程调用了 `stop()`方法修改了 flag 的值并不会立即刷新到主内存中,导致这个循环并不会立即停止。 131 | 132 | 这里主要利用的是 `volatile` 的内存可见性。 133 | 134 | 总结一下: 135 | - `volatile` 关键字只能保证可见性,顺序性,**不能保证原子性**。 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/test/java/com/crossoverjie/actual/LRUMapTest.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.actual; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sun.org.apache.bcel.internal.generic.LUSHR; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class LRUMapTest { 10 | 11 | @Test 12 | public void put() throws Exception { 13 | LRUMap lruMap = new LRUMap(3) ; 14 | lruMap.put("1",1) ; 15 | lruMap.put("2",2) ; 16 | lruMap.put("3",3) ; 17 | 18 | System.out.println(lruMap.toString()); 19 | 20 | lruMap.put("4",4) ; 21 | System.out.println(lruMap.toString()); 22 | 23 | lruMap.put("5",5) ; 24 | System.out.println(lruMap.toString()); 25 | } 26 | @Test 27 | public void put2() throws Exception { 28 | LRUMap lruMap = new LRUMap(4) ; 29 | lruMap.put("1",1) ; 30 | lruMap.put("2",2) ; 31 | lruMap.put("3",3) ; 32 | 33 | System.out.println(lruMap.toString()); 34 | 35 | lruMap.put("4",4) ; 36 | System.out.println(lruMap.toString()); 37 | 38 | lruMap.put("5",5) ; 39 | System.out.println(lruMap.toString()); 40 | } 41 | 42 | @Test 43 | public void put3() throws Exception { 44 | LRUMap lruMap = new LRUMap(4) ; 45 | lruMap.put("1",1) ; 46 | lruMap.put("2",2) ; 47 | lruMap.put("3",3) ; 48 | lruMap.put("2",2) ; 49 | 50 | System.out.println(lruMap.toString()); 51 | 52 | lruMap.put("4",4) ; 53 | System.out.println(lruMap.toString()); 54 | 55 | lruMap.put("5",5) ; 56 | System.out.println(lruMap.toString()); 57 | } 58 | 59 | @Test 60 | public void put4() throws Exception { 61 | LRUMap lruMap = new LRUMap(3) ; 62 | lruMap.put("1",1) ; 63 | lruMap.put("2",2) ; 64 | lruMap.put("3",3) ; 65 | 66 | System.out.println(lruMap.toString()); 67 | lruMap.put("2",2) ; 68 | 69 | System.out.println(lruMap.toString()); 70 | 71 | } 72 | 73 | @Test 74 | public void get() throws Exception { 75 | LRUMap lruMap = new LRUMap(3) ; 76 | lruMap.put("1",1) ; 77 | lruMap.put("2",2) ; 78 | lruMap.put("3",3) ; 79 | 80 | System.out.println(lruMap.toString()); 81 | System.out.println("=============="); 82 | 83 | Integer integer = lruMap.get("1"); 84 | System.out.println(integer); 85 | System.out.println("=============="); 86 | System.out.println(lruMap.toString()); 87 | } 88 | 89 | @Test 90 | public void get2() throws Exception { 91 | LRUMap lruMap = new LRUMap(3) ; 92 | lruMap.put("1",1) ; 93 | lruMap.put("2",2) ; 94 | lruMap.put("3",3) ; 95 | 96 | System.out.println(lruMap.toString()); 97 | System.out.println("=============="); 98 | 99 | Integer integer = lruMap.get("2"); 100 | System.out.println(integer); 101 | System.out.println("=============="); 102 | System.out.println(lruMap.toString()); 103 | } 104 | 105 | @Test 106 | public void get3() throws Exception { 107 | LRUMap lruMap = new LRUMap(3) ; 108 | lruMap.put("1",1) ; 109 | lruMap.put("2",2) ; 110 | lruMap.put("3",3) ; 111 | 112 | System.out.println(lruMap.toString()); 113 | System.out.println("=============="); 114 | 115 | Integer integer = lruMap.get("3"); 116 | System.out.println(integer); 117 | System.out.println("=============="); 118 | System.out.println(lruMap.toString()); 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/main/java/com/crossoverjie/red/RedPacket.java: -------------------------------------------------------------------------------- 1 | package com.crossoverjie.red; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | /** 7 | * Function: 模拟微信红包生成,以分为单位 8 | * 9 | * @author crossoverJie 10 | * Date: 03/01/2018 16:52 11 | * @since JDK 1.8 12 | */ 13 | public class RedPacket { 14 | 15 | /** 16 | * 生成红包最小值 1分 17 | */ 18 | private static final int MIN_MONEY = 1 ; 19 | 20 | /** 21 | * 生成红包最大值 200人民币 22 | */ 23 | private static final int MAX_MONEY = 200 * 100 ; 24 | 25 | /** 26 | * 小于最小值 27 | */ 28 | private static final int LESS = -1 ; 29 | /** 30 | * 大于最大值 31 | */ 32 | private static final int MORE = -2 ; 33 | 34 | /** 35 | * 正常值 36 | */ 37 | private static final int OK = 1 ; 38 | 39 | /** 40 | * 最大的红包是平均值的 TIMES 倍,防止某一次分配红包较大 41 | */ 42 | private static final double TIMES = 2.1F ; 43 | 44 | private int recursiveCount = 0 ; 45 | 46 | private List splitRedPacket(int money,int count){ 47 | List moneys = new LinkedList<>() ; 48 | 49 | //计算出最大红包 50 | int max = (int) ((money / count) * TIMES) ; 51 | max = max > MAX_MONEY ? MAX_MONEY : max ; 52 | 53 | for (int i = 0 ; i< count ; i++){ 54 | //随机获取红包 55 | int redPacket = randomRedPacket(money, MIN_MONEY,max,count - i) ; 56 | moneys.add(redPacket); 57 | //总金额每次减少 58 | money -= redPacket ; 59 | } 60 | 61 | return moneys ; 62 | } 63 | 64 | private int randomRedPacket(int totalMoney, int minMoney, int maxMoney, int count) { 65 | //只有一个红包直接返回 66 | if (count == 1){ 67 | return totalMoney ; 68 | } 69 | 70 | if (minMoney == maxMoney){ 71 | return minMoney ; 72 | } 73 | 74 | //如果最大金额大于了剩余金额 则用剩余金额 因为这个 money 每分配一次都会减小 75 | maxMoney = maxMoney > totalMoney ? totalMoney : maxMoney ; 76 | 77 | //在 minMoney到maxMoney 生成一个随机红包 78 | int redPacket = (int) (Math.random() * (maxMoney - minMoney) + minMoney) ; 79 | 80 | int lastMoney = totalMoney - redPacket ; 81 | 82 | int status = checkMoney(lastMoney,count - 1) ; 83 | 84 | //正常金额 85 | if (OK == status){ 86 | return redPacket ; 87 | } 88 | 89 | //如果生成的金额不合法 则递归重新生成 90 | if (LESS == status){ 91 | recursiveCount ++ ; 92 | System.out.println("recursiveCount==" + recursiveCount); 93 | return randomRedPacket(totalMoney,minMoney,redPacket,count) ; 94 | } 95 | 96 | if (MORE == status){ 97 | recursiveCount ++ ; 98 | System.out.println("recursiveCount===" + recursiveCount); 99 | return randomRedPacket(totalMoney,redPacket,maxMoney,count) ; 100 | } 101 | 102 | return redPacket ; 103 | } 104 | 105 | /** 106 | * 校验剩余的金额的平均值是否在 最小值和最大值这个范围内 107 | * @param lastMoney 108 | * @param count 109 | * @return 110 | */ 111 | private int checkMoney(int lastMoney, int count) { 112 | double avg = lastMoney / count ; 113 | if (avg < MIN_MONEY){ 114 | return LESS ; 115 | } 116 | 117 | if (avg > MAX_MONEY){ 118 | return MORE ; 119 | } 120 | 121 | return OK ; 122 | } 123 | 124 | 125 | public static void main(String[] args) { 126 | RedPacket redPacket = new RedPacket() ; 127 | List redPackets = redPacket.splitRedPacket(20000, 100); 128 | System.out.println(redPackets) ; 129 | 130 | int sum = 0 ; 131 | for (Integer red : redPackets) { 132 | sum += red ; 133 | } 134 | System.out.println(sum); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.crossoverjie.interview 8 | interview 9 | 1.0.0-SNAPSHOT 10 | jar 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 1.5.6.RELEASE 16 | 17 | 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 24 | UTF-8 25 | 2.5.3 26 | 1.8 27 | 4.3.10.RELEASE 28 | UTF-8 29 | 1.5.6.RELEASE 30 | 1.2.3 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-configuration-processor 39 | ${springboot.version} 40 | true 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-devtools 46 | ${springboot.version} 47 | true 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-aop 52 | ${springboot.version} 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | ${springboot.version} 59 | test 60 | 61 | 62 | 63 | junit 64 | junit 65 | 4.8.2 66 | test 67 | 68 | 69 | 70 | 71 | cglib 72 | cglib 73 | 3.2.5 74 | 75 | 76 | 77 | log4j 78 | log4j 79 | 1.2.17 80 | 81 | 82 | org.apache.commons 83 | commons-lang3 84 | 3.4 85 | 86 | 87 | com.google.guava 88 | guava 89 | 19.0 90 | 91 | 92 | 93 | com.alibaba 94 | fastjson 95 | 1.1.40 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-compiler-plugin 105 | 2.3.2 106 | 107 | 1.8 108 | 1.8 109 | ${project.build.sourceEncoding} 110 | 111 | 112 | 113 | 114 | org.springframework.boot 115 | spring-boot-maven-plugin 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /MD/ArrayList.md: -------------------------------------------------------------------------------- 1 | # ArrayList/Vector 的底层分析 2 | 3 | ## ArrayList 4 | 5 | `ArrayList` 实现于 `List`、`RandomAccess` 接口。可以插入空数据,也支持随机访问。 6 | 7 | `ArrayList `相当于动态数据,其中最重要的两个属性分别是: 8 | `elementData` 数组,以及 `size` 大小。 9 | 在调用 `add()` 方法的时候: 10 | ```java 11 | public boolean add(E e) { 12 | ensureCapacityInternal(size + 1); // Increments modCount!! 13 | elementData[size++] = e; 14 | return true; 15 | } 16 | ``` 17 | 18 | - 首先进行扩容校验。 19 | - 将插入的值放到尾部,并将 size + 1 。 20 | 21 | 如果是调用 `add(index,e)` 在指定位置添加的话: 22 | ```java 23 | public void add(int index, E element) { 24 | rangeCheckForAdd(index); 25 | 26 | ensureCapacityInternal(size + 1); // Increments modCount!! 27 | //复制,向后移动 28 | System.arraycopy(elementData, index, elementData, index + 1, 29 | size - index); 30 | elementData[index] = element; 31 | size++; 32 | } 33 | ``` 34 | 35 | 36 | - 也是首先扩容校验。 37 | - 接着对数据进行复制,目的是把 index 位置空出来放本次插入的数据,并将后面的数据向后移动一个位置。 38 | 39 | 其实扩容最终调用的代码: 40 | ```java 41 | private void grow(int minCapacity) { 42 | // overflow-conscious code 43 | int oldCapacity = elementData.length; 44 | int newCapacity = oldCapacity + (oldCapacity >> 1); 45 | if (newCapacity - minCapacity < 0) 46 | newCapacity = minCapacity; 47 | if (newCapacity - MAX_ARRAY_SIZE > 0) 48 | newCapacity = hugeCapacity(minCapacity); 49 | // minCapacity is usually close to size, so this is a win: 50 | elementData = Arrays.copyOf(elementData, newCapacity); 51 | } 52 | ``` 53 | 54 | 也是一个数组复制的过程。 55 | 56 | 由此可见 `ArrayList` 的主要消耗是数组扩容以及在指定位置添加数据,在日常使用时最好是指定大小,尽量减少扩容。更要减少在指定位置插入数据的操作。 57 | 58 | ### 序列化 59 | 60 | 由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用。因此使用了 `transient` 修饰,可以防止被自动序列化。 61 | 62 | ```java 63 | transient Object[] elementData; 64 | ``` 65 | 66 | 因此 ArrayList 自定义了序列化与反序列化: 67 | 68 | ```java 69 | private void writeObject(java.io.ObjectOutputStream s) 70 | throws java.io.IOException{ 71 | // Write out element count, and any hidden stuff 72 | int expectedModCount = modCount; 73 | s.defaultWriteObject(); 74 | 75 | // Write out size as capacity for behavioural compatibility with clone() 76 | s.writeInt(size); 77 | 78 | // Write out all elements in the proper order. 79 | //只序列化了被使用的数据 80 | for (int i=0; i