4-23

内存模型

Java线程之间的通信由Java内存模型控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。
具体的规则可以看happenes-before。

要注意的点

  1. 缓存一致性协议(如MESI)和Java内存模型没有必然关系。JMM既是规范,也是抽象。
  2. Java从源码到运行可能经历编译器重排序、指令重排序、内存重排序。了解JMM使得你可以不必学习缓存一致性协议、CPU流水线机制与指令重排、内存屏障等概念,只需要记住happens before等一系列基本规则即可。

ConcurrentHashMap——put

  1. // put方法直接调用了这个
  2. final V putVal(K key, V value, boolean onlyIfAbsent) {
  3. if (key == null || value == null) throw new NullPointerException();
  4. //这里是高位与低位^操作。这是为了分布均衡。
  5. //否则的话如果单纯用hashcode,hashcode增长是有规律的。可能导致很多高位相关的桶利用率低。
  6. int hash = spread(key.hashCode());
  7. int binCount = 0;
  8. for (Node<K,V>[] tab = table;;) {
  9. Node<K,V> f; int n, i, fh; K fk; V fv;
  10. if (tab == null || (n = tab.length) == 0)
  11. //桶里没东西就初始化
  12. tab = initTable();
  13. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
  14. //如果桶里没有元素,CAS去设置头节点
  15. if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
  16. break; // no lock when adding to empty bin
  17. }
  18. else if ((fh = f.hash) == MOVED)
  19. //如果hash是MOVED,说明在扩容移动元素,则让当前线程参与扩容
  20. tab = helpTransfer(tab, f);
  21. else if (onlyIfAbsent // check first node without acquiring lock
  22. && fh == hash
  23. && ((fk = f.key) == key || (fk != null && key.equals(fk)))
  24. && (fv = f.val) != null)
  25. return fv;
  26. else {
  27. V oldVal = null;
  28. synchronized (f) {
  29. if (tabAt(tab, i) == f) {
  30. if (fh >= 0) {
  31. binCount = 1;
  32. for (Node<K,V> e = f;; ++binCount) {
  33. K ek;
  34. if (e.hash == hash &&
  35. ((ek = e.key) == key ||
  36. (ek != null && key.equals(ek)))) {
  37. oldVal = e.val;
  38. if (!onlyIfAbsent)
  39. e.val = value;
  40. break;
  41. }
  42. Node<K,V> pred = e;
  43. if ((e = e.next) == null) {
  44. pred.next = new Node<K,V>(hash, key, value);
  45. break;
  46. }
  47. }
  48. }
  49. else if (f instanceof TreeBin) {
  50. Node<K,V> p;
  51. binCount = 2;
  52. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
  53. value)) != null) {
  54. oldVal = p.val;
  55. if (!onlyIfAbsent)
  56. p.val = value;
  57. }
  58. }
  59. else if (f instanceof ReservationNode)
  60. throw new IllegalStateException("Recursive update");
  61. }
  62. }
  63. if (binCount != 0) {
  64. if (binCount >= TREEIFY_THRESHOLD)
  65. treeifyBin(tab, i);
  66. if (oldVal != null)
  67. return oldVal;
  68. break;
  69. }
  70. }
  71. }
  72. addCount(1L, binCount);
  73. return null;
  74. }

5.8

Synchronized和ReentratLock的区别

最直观的区别是,一个是关键字,一个是并发包里的类。
Lock有更好的拓展性,具体地,可以实现公平非公平、读写等针对业务场景的实现。
Synchronize是非公平锁,用户无法改变这点,这与它的底层实现有关。

此外,前者可以锁方法,后者只能锁代码块。

但Synchronized有个很大的优势,就是可以自动释放。因为忘记释放锁而产生死锁的场景是非常多的。

Synchronized底层如何实现?锁升级过程

https://blog.csdn.net/Ning862217083/article/details/123535229?spm=1001.2014.3001.5501
这里记录了以前的学习,大体记录了如何实现。主要是依靠虚拟机的monitor对象去抽象一个内核级的锁。(Linux的话,就是mutex)

锁升级过程网上已经有很多图了。其实主要就是修改对象头的markword。偏向锁就只修改markword成线程Id,轻量锁则是在线程栈里开辟空间,存储lock record,让他和markword互相引用。

线程池有哪几种?分别什么特点

https://www.cnblogs.com/gujiande/p/9488462.html
其实只要搞清楚线程池的构造器参数就可以了。

手写快速排序?多线程快速排序?

  1. private void quickSort(int[] arr, int l, int r) {
  2. if (l >= r) return;
  3. int i = l - 1, j = r + 1, mid = l + r >> 1;
  4. while (i < j) {
  5. while (arr[++i] < arr[mid]);
  6. while (arr[--j] > arr[mid]);
  7. if (i < j) {
  8. swap(arr, i, j);
  9. }
  10. }
  11. quickSort(arr, l, j); // have to be j!
  12. quickSort(arr, j+1, r);
  13. // 因为快排固定好了位置,所以左右递归可以让新的线程去做
  14. }

5.15

ForkJoinPool

fork就不多说了,和linux下创建子进程的系统调用是一个意思。克隆本身的树和fork的树也是一样的。
join意思是把结果合并。
总体来说是分派任务给多个线程去计算然后合并。
比较重要的一点是工作窃取,就是每个线程完成了自己的任务后还会去队列里取别的任务。

CF

cf主要是对Future的加强。类似于Stream对Collection的加强。
假如我们有三个异步任务,分别是a b c,我们想希望用a和b的结果作为参数传入c。
这种需求如果你用future做是比较麻烦的,但是用cf的类流式操作,可以很快很优雅的把各种任务combine到一起。

归并排序

  1. import java.util.Arrays;
  2. import java.util.stream.Collectors;
  3. public class Main {
  4. public static int[] tmp;
  5. public static void main(String[] args) {
  6. int[] arr = new int[]{2,3,4,5,1,7,8,6,9,10};
  7. tmp = new int[arr.length];
  8. mergeSort(arr, 0, arr.length-1);
  9. System.out.println(Arrays.stream(arr).boxed().collect(Collectors.toList()));
  10. }
  11. public static void mergeSort(int[] arr, int l, int r) {
  12. if (l >= r) {
  13. return;
  14. }
  15. int mid = l + r >> 1;
  16. mergeSort(arr, l, mid);
  17. mergeSort(arr, mid+1, r);
  18. merge(arr, l, r);
  19. }
  20. public static void merge(int[] arr, int l, int r) {
  21. if (l >= r) return;
  22. int mid = l + r >> 1, i = l, j = mid+1, k = l;
  23. while (i <= mid && j <= r) {
  24. if (arr[i] <= arr[j]) {
  25. tmp[k++] = arr[i++];
  26. } else {
  27. tmp[k++] = arr[j++];
  28. }
  29. }
  30. while (i <= mid) {
  31. tmp[k++] = arr[i++];
  32. }
  33. while (j <= r) {
  34. tmp[k++] = arr[j++];
  35. }
  36. for (i=l; i<=r; i++) {
  37. arr[i] = tmp[i];
  38. }
  39. }
  40. }
  41. public static void mergeSortAsync(int[] arr, int l, int r) {
  42. if (l >= r) return;
  43. int mid = l + r >> 1;
  44. ForkJoinTask<?> t1 = forkJoinPool.submit(() -> mergeSortAsync(arr, l, mid));
  45. ForkJoinTask<?> t2 = forkJoinPool.submit(() -> mergeSortAsync(arr, mid + 1, r));
  46. try {
  47. t1.get();
  48. t2.get();
  49. merge(arr, l, r);
  50. } catch (ExecutionException | InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. }

5.22

IoC

ioc是控制反转。控制指的是管理对象生命周期的能力,反转指的是把对象生命周期的管理交给容器。
IOC在编写大规模的程序时才能发挥力量。具体地,当大部分对象的创建都依赖于大量其他对象时,如果手动去set是很劳累的事情。

解决循环依赖

其实这很自然

  1. A a = new A();
  2. B b = new B();
  3. a.setB(b);
  4. b.setA(a);

只要我们先创建对象,后调用setter就可以了。
https://blog.csdn.net/weixin_43966635/article/details/118608808

AoP

面向切面编程,意思是我们需要提取一些公共操作,然后让所有符合条件的函数都去执行这些操作。
这是一种很常见的编程思想。我们经常会把打印日志等操作抽取成公共的。
像Golang的gin框架大量的使用了中间件这个概念,一个中间件可以被认为是一个函数。在每个操作后调用这些函数即可。

具体地, AoP的实现依赖于动态代理。动态代理的实现方法有Jdk自带和Cglib修改字节码这两种。如果用Jdk动态代理要求被代理对象实现了某个接口。

5.29

画循环依赖的图

hw - 图1

SpringMVC执行流程

image.png

SpringBoot与SpringFramework的关系

SpringBoot简化了配置,日常开发大多数的参数是不会变动的,这部分参数就用默认值。
也就是“约定优于配置”。即使需要配置的部分,大多也以注解完成。

6.12

spring boot 启动流程

说实话实在太长了。。。
启动流程

spring boot如何内嵌tomcat

内嵌Tomcat

6.19

SpringCloud 常用组件

https://www.kancloud.cn/hanxt/springcloud/1599297

CAP

CAP分别是一致性、可用性、分区容错性。
三者只能取其中二者。
一些具体解释

分布式一致性算法

主要是raft和pacos的变体
分布式一致性算法