0%

2018年4月1日 上午11:52

  1. 它是一种规范
  2. 他规定了虚拟机与计算机内存是如何协同工作的。它规定了一个线程如何和何时可以看到由其他线程修改过的共享变量的值。以及在必须时如何同步的访问共享变量

  1. 我们需要先弄清图片里面的两个JVM 内存分配的两个概念
  2. java里的的呢,他是一个运行时的数据区,堆是由垃圾回收来负责的。
    1. 堆它的优势呢,是可以动态的分配内存大小生存期也不必事先告诉编译器,因为他是在运行时动态分配内存了,java的垃圾收集器会自动收走这些不再使用的数据。
    2. 但是也有缺点的,由于是要在运行时动态分配内存,因此受到存取速度相对慢一些
  3. 这里的
    1. 在它的优势是存取速度比堆要快。仅次于计算机里的寄存器,栈的数据还是可以共享的。
    2. 但是它的缺点呢,是存在栈中的数据大小与生存期必须是确定的。缺乏一些灵活性
    3. 栈主要存放一些基本类型的变量。比如我们小写的int,short,byte,float,double,char和对象句柄。
  4. java内存模型呢
    1. 要求调用栈和本地内存变量存放在线程栈(Thread Stack)上
    2. 对象存放在堆上。
  5. 我们具体说一下。一个本地变量,他也可能是指向一个对象的引用
    1. 这种情况下引用这个本地变量,他是存放在这个线程栈上。
    2. 但是对象本身他是存放在堆上的。一个对象,他可能包含方法之类的,这方法可能包含本地变量。这里面local variable1,local variable2。
    3. 这些本地变量,他仍然是存放在线程栈上的即使这些方法所属的对象存放在堆上
    4. 一个对象的成员变量可能会随着这个对象自身存放在堆上。不管这个成员变量是原始类型,还是引用类型,静态成员变量跟随类的定义 一起存放在堆上。
    5. 存放在堆上对象可以被所持有对这个对象引用的线程访问。这边,如果我们这个Thread的存放了object的一个引用,那么它是可以访问他的。
    6. 一个线程可以访问一个对象的时候呢,他也可以访问这个对象的成员变量。
    7. 两个线程同时调用同一个对象的同一个方法,他们将会都访问这个对象的成员变量。但是每一个线程都拥有了这个成员变量私有拷贝。这个特别重要,就是如果两个线程里面,比如这个线程和这个线程,他们同时调用了同一个对象,这里object3,他们的同一个方法,他们都会访问这个对象的成员变量。但是呢,这两个线程他们都拥有的是这个成员变量的私有拷贝,记住了,这里是私有拷贝。
  6. 我们说一下他们的运作原理。
  7. 通常情况下,::当一个CPU需要读取主存的时候呢,它会将主存的部分数据读取到CPU缓存中。他甚至可能将缓存的部分内容读到它的内部寄存器里面。然后在寄存器中执行操作。当CPU需要将结果回起到主存的时候。他会将内部寄存器的值刷新到缓存中,然后在某个时间点就刷新回主存::
  8. 下来呢,我们来看一下java内存模型和硬件的内存之间的一些关联。
  9. 通过图呢,我们可以看出来的java内存模型与硬件内存之间存在一些差异的。硬件内存架构,它没有区分栈和堆。对于硬件而言,所有的线程,栈和堆都分布在总内存里面。有时候栈和堆可能有时候会出现在CPU缓存中和CPU内部的寄存器里面。
  10. 来看一下线程和主内存之间的抽象的关系
  11. ::线程之间的共享变量,它存储在我们的主内存里面,每个线程都有一个私有的本地内存::。
  12. 本地内存,他是java内存模型的一个抽象的概念,他并不是真实存在的,它涵盖了缓存,写缓存区区,寄存器以及其他的硬件和编译器的优化。
  13. 本地内存中他存储了该线程中已读或写, 共享变量的拷贝的一个副本
    1. 比如,这里面线程如要使用主内存中的一个共享变量。他先拷贝出来这个共享变量的一个副本放在自己的本地的里面。
    2. 从更低的层次来说,主内存就是硬件的内存。是为了获取更好的运行速度。
    3. 虚拟机及硬件系统系统可能会让工作内存优先,存储于寄存器和高速缓存中。
  14. 那什么是的线程的工作内存
    1. java内存模型中线程的工作内存,是CPU的寄存器和高速缓存的一个抽象的描述。
    2. 而JVM呢。静态内存存储模型就是我们JVM内存模型。它只是一种对内存的物理划分而已,他只局限在内存,而且只局限在JVM的内存。
    3. 现在呢,如果线程间通信。他要求必须要经过主内存
  15. ::如果线程a和线程b之间要通信的话,必须要经历下面两个步骤。::
    1. 第一步是线程a,要把本地的内存a中更新过的共享变量刷新到主内存里面去。
    2. 线程b到主内存中去读取线程a之前已经更新过的共享变量。
  16. 听完这个这个可能许多人已经意识到之前演示的技术功能问题出在哪里。






  17. 首先java内存模型是规范。
  18. 它规定了一个线程如何和何时可以到有其他线程修改过后的共享变量的值
  19. 以及在必须时,如何同步的访问共享变量
  20. 他要求本地变量存放在线程栈上,对象存放在堆上。
  21. 线程间通信必须要经过主内存。
  22. 同时他定义了同步的八个操作及基本规则。
  23. 这个呢,那我们处理并发问题提供了理论基础。后面我们对它的并发编程的一些手段都是基于java内存模型的这个规范的。

2018年4月1日 上午11:34


由于main mamory和cache的速度差异被拉大,所以加入了多级缓存。



参考:

  1. 【4】Java并发编程:多线程中的缓存一致性和CAS - CSDN博客

  2. 慕课网高并发实战(二)-并发基础 - 简书

  3. 他是个协议,MESI这个协议,为了保证多个CPU缓存中共享数据的一致性

  4. 定义了cache的四种状态

  5. CPU对cache的四种操作可能会产生不一致的状态

  6. 因此缓存控制器,监听到本地操作和远程操作的时候。需要对cache状态做出一定的修改,从而保证数据在多个缓存之间流转的一致性

  7. SEMI其实是四个状态的缩写。

四种cache状态:2*2=4(是否独有 * 是否与内存一致)

  1. M:Modified被修改的。
    1. 处于这一状态的数据,只在本CPU中有缓存数据,而其他CPU中没有。
    2. 同时其状态相对于内存中的值来说,是已经被修改的,且没有更新到内存中。
  2. E:Exclusive独占的。
    1. 处于这一状态的数据,只有在本CPU中有缓存,
    2. 且其数据没有修改,即与内存中一致。
  3. S:Share共享的。
    1. 处于这一状态的数据在多个CPU中都有缓存
    2. 且与内存一致。
  4. I:Invalid 无效的。
    1. 本CPU中的这份缓存已经无效。

这里首先介绍该协议约定的缓存上对应的监听:

  1. 自己的状态受别的CPU影响
    1. 一个处于M状态的缓存行
      1. 必须时刻监听,所有试图读取该缓存行对应的主存地址的操作
      2. 如果监听到,则必须在此操作执行前把其缓存行中的数据写回CPU
    2. 一个处于S状态的缓存行
      1. 必须时刻监听,使该缓存行无效或者独享该缓存行的请求
      2. 如果监听到,则必须把其缓存行状态设置为I
    3. 一个处于E状态的缓存行
      1. 必须时刻监听,其他试图读取该缓存行对应的主存地址的操作
      2. 如果监听到,则必须把其缓存行状态设置为S
  2. 当CPU需要读取数据时
    1. 一个invalid的缓存必须从主存中读取(变成S或者E状态)来满足该CPU的读请求
    2. 如果不是I,则可以直接读取缓存中的值,
      1. 但在此之前,必须要等待其他CPU的监听结果
      2. 如其他CPU也有该数据的缓存且状态是M,则需要等待其把缓存更新到内存之后,再读取。
  3. 当CPU需要写数据
    1. 只有在其缓存行是M或者E的时候才能执行
    2. 否则需要发出特殊的RFO指令(Read Or Ownership,这是一种总线事务),通知其他CPU置缓存无效(I),这种情况下会性能开销是相对较大的。
    3. 在写入完成后,修改其缓存状态为M
    4. 所以如果一个变量在某段时间只被一个线程频繁地修改,则使用其内部缓存就完全可以办到,不涉及到总线事务,
    5. 如果缓存一会被这个CPU独占、一会被那个CPU 独占,这时才会不断产生RFO指令影响到并发性能。
    6. 这里说的缓存频繁被独占并不是指线程越多越容易触发,而是这里的CPU协调机制,这有点类似于有时多线程并不一定提高效率,原因是线程挂起、调度的开销比执行任务的开销还要大,这里的多CPU也是一样,如果在CPU间调度不合理,也会形成RFO指令的开销比任务开销还要大。当然,这不是编程者需要考虑的事,操作系统会有相应的内存地址的相关判断,这不在本文的讨论范围之内。
  4. 并非所有情况都会使用缓存一致性的,如被操作的数据不能被缓存在CPU内部或操作数据跨越多个缓存行(状态无法标识),则处理器会调用总线锁定;另外当CPU不支持缓存锁定时,自然也只能用总线锁定了,比如说奔腾486以及更老的CPU。

2018年4月1日 上午11:24

别人好的笔记:

并发编程 - 专题 - 简书
专栏:技术探索 - 慕课网实战·高并发 - CSDN博客

自己的总结:

2018年11月19日 下午5:57
《Java并发编程与高并发解决方案》课程相关手记汇总 - 持续更新_慕课手记
脑子中要记住这个图,这是整个知识的脉络。这幅图中的除了补充内容以外,我都学完了,并且笔记做的还是不错的。

2020年5月22日 下午9:37
重新认识原子量内存序
重新认识“锁”这个字

2019年3月25日 下午1:00
并发容器中的分段锁
容器与线程池的区别

2018年8月19日 下午7:31
老师总结:多线程并发的使用、学习与测试

关于JDK层中代码的一点认识
J.U.C知识点脉络图
线程池
组件FutureTask、ForkJoin、BlockingQueue

心态的调节

AQS相关重点
读写锁和乐观读+ StampedLock放在一起理解
锁的可重入性
06 锁:可重入锁 公平锁 读写锁
Collection<? extends E>
前面的重要问题提炼
重新理解一下java内存模型(JMM)

如何理解线程这个东西
java的初始化过程(有静态变量和方法)
java堆栈的理解
java中的static关键字

同步容器也会线程不安全
线程安全的策略
中期对并发的整理总结
安全发布对象
对MESI和JMM的认识
线程安全性
qq群问答
并发模拟-代码
并发工具
环境初始化
并发的优势与风险
JAVA内存模型
CPU多级缓存-乱序执行优化
CPU多级缓存-缓存一致性
并发和高并发的基本概念
课程介绍

资料:

《Java并发编程与高并发解决方案》知识点索引_慕课手记
《Java开发企业级权限管理系统》问题汇总 - 持续更新中_慕课手记

Java并发编程与高并发解决方案.svg

2018年3月31日 上午11:41

对RESTful的理解:

  1. 其实就是要创建一种RESTful风格的URL
  2. 参数全部暴露出来,通过斜杠的方式。_斜杠_就是RESTful的主要表现方式
  3. 原先URL通过.do的方式,URL指向的是一个方法,而RESTful风格的URL指向的是一个资源。
  4. 我理解RESTful就是一种定位到方法的新的方式,这种方式是springMVC本身就支持的。而我们最先使用的detail.do?productId=26这种.do的方式,也是springMVC自带的其中一种而已。

不适合用RESTful的情况:

  1. 方法需要的参数过多的时候,我们就必须在URL中写那么多的参数,看见就不舒服。
  2. RESTful中声明的参数是不可以为空的

SpringMVC_RESTful配置

一些例子:

下面的代码中有两个是bad例子,作为对比,很好理解,我就不带了写了,关键是理解清楚RESTful是干啥的就行了。


资源定位不准确,restfull不予许为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package com.mmall.controller.portal;

import com.github.pagehelper.PageInfo;
import com.mmall.common.ServerResponse;
import com.mmall.service.IProductService;
import com.mmall.vo.ProductDetailVo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* Created by geely
*/

@Controller
@RequestMapping("/product/")
public class ProductController {

@Autowired
private IProductService iProductService;



@RequestMapping("detail.do")
@ResponseBody
public ServerResponse<ProductDetailVo> detail(Integer productId){
return iProductService.getProductDetail(productId);
}


@RequestMapping(value = "/{productId}", method = RequestMethod.GET)
@ResponseBody
public ServerResponse<ProductDetailVo> detailRESTful(@PathVariable Integer productId){
return iProductService.getProductDetail(productId);
}

@RequestMapping("list.do")
@ResponseBody
public ServerResponse<PageInfo> list(@RequestParam(value = "keyword",required = false)String keyword,
@RequestParam(value = "categoryId",required = false)Integer categoryId,
@RequestParam(value = "pageNum",defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize",defaultValue = "10") int pageSize,
@RequestParam(value = "orderBy",defaultValue = "") String orderBy){
return iProductService.getProductByKeywordCategory(keyword,categoryId,pageNum,pageSize,orderBy);
}


//http://www.happymmall.com/product/手机/100012/1/10/price_asc
@RequestMapping(value = "/{keyword}/{categoryId}/{pageNum}/{pageSize}/{orderBy}",method = RequestMethod.GET)
@ResponseBody
public ServerResponse<PageInfo> listRESTful(@PathVariable(value = "keyword")String keyword,
@PathVariable(value = "categoryId")Integer categoryId,
@PathVariable(value = "pageNum") Integer pageNum,
@PathVariable(value = "pageSize") Integer pageSize,
@PathVariable(value = "orderBy") String orderBy){
if(pageNum == null){
pageNum = 1;
}
if(pageSize == null){
pageSize = 10;
}
if(StringUtils.isBlank(orderBy)){
orderBy = "price_asc";
}

return iProductService.getProductByKeywordCategory(keyword,categoryId,pageNum,pageSize,orderBy);
}


// http://www.happymmall.com/product/100012/1/10/price_asc
@RequestMapping(value = "/{categoryId}/{pageNum}/{pageSize}/{orderBy}",method = RequestMethod.GET)
@ResponseBody
public ServerResponse<PageInfo> listRESTfulBadcase(@PathVariable(value = "categoryId")Integer categoryId,
@PathVariable(value = "pageNum") Integer pageNum,
@PathVariable(value = "pageSize") Integer pageSize,
@PathVariable(value = "orderBy") String orderBy){
if(pageNum == null){
pageNum = 1;
}
if(pageSize == null){
pageSize = 10;
}
if(StringUtils.isBlank(orderBy)){
orderBy = "price_asc";
}

return iProductService.getProductByKeywordCategory("",categoryId,pageNum,pageSize,orderBy);
}


@RequestMapping(value = "/{keyword}/{pageNum}/{pageSize}/{orderBy}",method = RequestMethod.GET)
@ResponseBody
public ServerResponse<PageInfo> listRESTfulBadcase(@PathVariable(value = "keyword")String keyword,
@PathVariable(value = "pageNum") Integer pageNum,
@PathVariable(value = "pageSize") Integer pageSize,
@PathVariable(value = "orderBy") String orderBy){
if(pageNum == null){
pageNum = 1;
}
if(pageSize == null){
pageSize = 10;
}
if(StringUtils.isBlank(orderBy)){
orderBy = "price_asc";
}

return iProductService.getProductByKeywordCategory(keyword,null,pageNum,pageSize,orderBy);
}


//http://www.happymmall.com/product/keyword/手机/1/10/price_asc
@RequestMapping(value = "/keyword/{keyword}/{pageNum}/{pageSize}/{orderBy}",method = RequestMethod.GET)
@ResponseBody
public ServerResponse<PageInfo> listRESTful(@PathVariable(value = "keyword")String keyword,
@PathVariable(value = "pageNum") Integer pageNum,
@PathVariable(value = "pageSize") Integer pageSize,
@PathVariable(value = "orderBy") String orderBy){
if(pageNum == null){
pageNum = 1;
}
if(pageSize == null){
pageSize = 10;
}
if(StringUtils.isBlank(orderBy)){
orderBy = "price_asc";
}

return iProductService.getProductByKeywordCategory(keyword,null,pageNum,pageSize,orderBy);
}


//http://www.happymmall.com/product/category/100012/1/10/price_asc
@RequestMapping(value = "/category/{categoryId}/{pageNum}/{pageSize}/{orderBy}",method = RequestMethod.GET)
@ResponseBody
public ServerResponse<PageInfo> listRESTful(@PathVariable(value = "categoryId")Integer categoryId,
@PathVariable(value = "pageNum") Integer pageNum,
@PathVariable(value = "pageSize") Integer pageSize,
@PathVariable(value = "orderBy") String orderBy){
if(pageNum == null){
pageNum = 1;
}
if(pageSize == null){
pageSize = 10;
}
if(StringUtils.isBlank(orderBy)){
orderBy = "price_asc";
}

return iProductService.getProductByKeywordCategory("",categoryId,pageNum,pageSize,orderBy);
}
}

2018年3月31日 下午6:25









也可以让网页自动生成cron








配置spring schedule


关单的操作的编写

  1. 按时间查出到期的订单
  2. 产品库存
    1. 需要写新的服务方法在IOderService中,和sql语句。
    2. 为了保证库存的一致性使用for update行锁
      1. 一定要用主键查,并且使用mysql的InnoDB引擎
  3. 更新每个产品的库存
  4. 更改订单的状态status,使之关闭

    创建类tack/CloseOrderTask.java类

    1. 在这个类中写关单的流程
    2. 要在配置文件property中声明一些重要的配置常量

附带的操作:

  1. 解决配置文件中外部属性无法正确显示(不影响运行)

2018年3月30日 下午4:22

这节的内容只是让你学会一个方法,在项目中我们不用






启动各种服务:

  1. 启动nginx
    1. 访问localhost,观察nginx页面
  2. 启动两个redis
    1. 两个端口要不同!
  3. 访问happymmall.com
    1. 此时502,因为tomcat还没有启动
    2. 这里已经配置host文件
    3. 这是要ping 一下happymmall.com,看是否是指向127.0.0.1
  4. 分别启动tomcat1,2
    1. 不要同时启动
    2. 两个tomcat的端口不同

使用spring-session和调试方法

RedisDesktopManager工具

这个端口要求:

  1. 防火墙开放
  2. 安全组开放

    可以理解是一个树状结构


查找写法,修改pom,此时最好关闭tomcat。








第一次加载源码,可能需要我们点击idea弹出的download选项

定位之后可以看到源码的包构成


详细的文档页


例子运行起来,配合上手册,打断点进行调试。这就是学习源码的方式









这是启动tomcat会报错


这时也是debug启动!
并且只启动一个tomcat,访问方式变为:localhost:8080



command+U
观察对象的值。
然后,单步调试。在这个过程中要观察窗口的输出情况。

说明我们的代码要实现serializable接口

重启tomcat,重新访问,再把monitor打开




然后在debug模式下一直下一步,跳入源码,看源码的执行步骤。
如何跳入源码?

  1. 先找到文件,在源码文加上打断点。
  2. 或者一步步跳入,再打断点。


关闭tomcat,然后开始看源码