0%

锁解决的问题是并发操作引起的脏读、数据不一致问题。

基本原理

volatile

在Java中允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保使用排它锁来单独获得这个变量,Java中提供了 volatile,使之在多处理器开发中保证变量的可见性,当一个线程改变了共享变量,另一个线程能够及时读到这个修改的值。恰当的使用它会比 synchronized 成本更低,因为不会引起上下文的切换和调度。

synchronized

通过锁机制实现同步,在Java中每一个对象都可以作为锁,有以下三种形式:

  • 对于普通同步方法,锁的是当前实例对象。
  • 对于静态同步方法,所得是当前类 class 对象。
  • 对于同步方法块,锁的是括号内指定的对象。

为了减少获得锁和释放锁带来的性能消耗,Java SE 1.6 引入了偏向锁和轻量级锁。偏向锁 的核心思想是:如果一个线程获得了锁,就进入偏向模式,当这个线程再次请求锁时,如果没有其它线程获取过该锁,无需再做任何同步操作,可以节省大量锁申请的操作,来提高性能。如果偏向锁获取失败,会通过 轻量级锁 的方式获取,如果获取成功则进入临界区,如果失败则表示有其它线程争夺到锁,当前线程锁请求会膨胀为 重量级锁

锁粗化 是指在遇到一连串连续的对同一个锁不断的进行请求和释放的操作时,会把所有的锁操作整合成对锁的一次请求,减少锁请求的同步次数。

锁消除 是指在编译期,通过对上下文的扫描,去除不可能存在共享资源竞争的锁。

自旋锁 是指在锁膨胀后,避免线程真正的在操作系统层面被挂起,通过对线程做几个空循环,以期望在这之后能获取到锁,顺利的进入临界区,如果还获取不到,则会真正被操作系统层面挂起。

CAS

指的是比较并交换,它是一个原子操作,比较一个内存位置的值并且只有相等时修改这个内存位置的值并更新值,保证新的值总是基于最新的信息计算的。在 JVM 中 CAS 操作是利用处理器提供的 CMPXCHS 指令实现。是实现我们平时所说的自旋锁或乐观锁的核心操作。

优点是竞争小的时候使用系统开销小;对应缺点是循环时间长开销大、ABA问题、只能保证一个变量的原子操作。

ABA 问题

问题产生原因是两个线程处理的时间差导致,具体如下图:

解决 ABA 问题可以增加一个版本号,在每次修改值的时候增加一个版本号。

产生:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);

public static void main(String[] args) {
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
},"t1").start();

new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t修改后的值:" + atomicReference.get());
},"t2").start();
}

解决:

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
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1);

public static void main(String[] args) {
new Thread(() -> {
System.out.println("t1拿到的初始版本号:" + atomicStampedReference.getStamp());

//睡眠1秒,是为了让t2线程也拿到同样的初始版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
},"t1").start();

new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("t2拿到的初始版本号:" + stamp);

//睡眠3秒,是为了让t1线程完成ABA操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最新版本号:" + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "\t当前 值:" + atomicStampedReference.getReference());
},"t2").start();
}

在 mac 机器上可以使用 mweb 来写博客,比较好用的地方就是可以直接把剪贴板的图片粘贴上来,缺点是 mac 键盘超难用并且不支持窗口内开启命令行。平时在家的时候都用 Ubuntu 台式机,博客使用 VS Code 编写,一直以来阻挡我的是图片的粘贴特别费劲,今天发现一个很好用的插件 pasteimage,可以直接将剪贴板图片粘贴到 markdown 使用,并且支持配置保存路径。

然后按照教程配置好参数:

1
2
3
4
5
6
{
"pasteImage.path": "${projectRoot}/source/resource/img",
"pasteImage.basePath": "${projectRoot}/source",
"pasteImage.forceUnixStyleSeparator": true,
"pasteImage.prefix": "/"
}

就可以直接将图片粘贴到 markdown 中,其中遇到个问题就是配置不生效,会导致文件直接保存到当前文件目录,具体配置方法可以参考下面连接。

https://www.crifan.com/vscode_how_to_config_setting_plugin/ 这篇文章写的很详细了。
https://github.com/mushanshitiancai/vscode-paste-image 这篇是配置教程,里面有些地方比较容易被误导。

对于Linux系统需要有 xclip 支持,使用的时候会给提示的。

另外记录一下 Ubuntu 的截屏和粘贴快捷键:

1
2
Ctrl + Shift + Print Screen  // 区域截屏到剪贴板
Ctrl + Alt + s // 在 VS Code 中粘贴

设计原则

如果把设计模式理解为优秀软件系统模块设计的最小抽象,那么设计原则就是这些抽象的指导思想。目的是设计一个易于扩展和维护的系统。设计模式的六大原则有:

  • Single Responsibility Principle:单一职责原则
  • Open Closed Principle:开闭原则
  • Liskov Substitution Principle:里氏替换原则
  • Law of Demeter:迪米特法则
  • Interface Segregation Principle:接口隔离原则
  • Dependence Inversion Principle:依赖倒置原则

单一职责原则

应该有且只有一个原因引起类的变化,一个类只负责一个职责。一个功能应该要划分成多少个职责类去实现,并没有明显的限定。举例说明对于用户管理,用户信息管理和用户行为管理可以做初步拆分,用户信息管理又可以拆分成普通信息维护和敏感信息的维护。又比如用户发生一笔支付行为可以初步拆分为交易信息管理和支付信息管理。职责划分的粗细的影响因素有对于业务理解程度、项目开发阶段等,过粗会造成一个处理类包含太多职责,过细又会增加开发维护成本。单一职责是“高内聚低耦合”设计的一种实现形式,单一职责即为同一职责内部的内聚性,降低不同职责之间的耦合性。

里氏替换原则

描述继承关系,子类全部实现或继承父类方法,子类可以扩展父类方法实现,将子类替换父类不会产生异常。在重构角度来说如果多个子类拥有相同的行为,可以将相同行为提取到父类实现,子类调用扩展父类实现。在开发上基于“组合大于继承”的原则,通过定义实现接口的形成被其它类调用。违反这个原则不一定会产生严重后果,但是会对后面的开发维护造成困难。

开闭原则

描述的是对于需求产生变化后,软件实体部分应该进行扩展开发,避免修改。通过扩展实体行为来响应需求变化,而不是通过修改现有软件代码。

迪米特法则

描述的是一个对象应该进行减少对于其它对象的理解。通过封装我们可以屏蔽类内部逻辑,只提供足够用且少量的方法来给外部使用,降低对象之间的耦合性。当一个接口或者一个对象被公开,意味着后面我们进行开发和维护的时候很难再将这个对象收回,重构内部逻辑时也会更加困难。

接口隔离原则

描述的是建立单一的接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量行为统一,避免臃肿。对于支付接口来说,定义类通用支付方法,对于获取分期支付信息也属于支付行为的一个环节,但不是所有实体类必须要实现的,可以拆分出来。

依赖倒置原则

描述的是实现类之间不能直接发生依赖关系,其依赖关系是通过接口或者抽象类产生,即面向接口编程。实现类依赖接口或者抽象类,而接口或者抽象类不依赖实现类。

设计模式

https://design-patterns.readthedocs.io/zh_CN/latest/index.html

一些小的点

  1. 坚持阅读英文技术资料,并至少翻译12篇技术文章。x
  2. 戒掉熬夜的坏习惯,习惯性去健身,保持精力的充沛。60%
  3. 刻意提高专注力,并坚持系统性的做技术储备。80%
  4. 常出去走走,看看外面的世界,准备至少两次的远途旅行。100%
  5. 找到一个互相合适的伴侣。100%
  6. 趁年轻努力挣钱,但是不要透支身体。50%
  7. 好好学说话,做一个有趣的人。60%

主要方向

  1. 英语学习,坚持阅读英文技术资料,至少翻译12篇技术文章,基本的口语表达。x
  2. 沟通能力,说话前多思考,减少小动作的出现,改变不好的下意识动作和反应。50%
  3. 正确锻炼,学会并养成跑步习惯,无氧健身增加身体健壮。90%

发音练习

《张浩翔 魅力声音必修课》
预计学习时间 2 天,整理笔记,刻意练习。注意学习的几个阶段。

做到了哪些

  1. 考了救护员证
  2. 去了天津、昆明、大理、丹东
  3. 跑了马拉松
  4. 去山顶露营

第一个半马

云南之旅

丹东之旅

救护员证

  • 六本本职技术书籍/专题
    • effective java done 01.29
    • Java 并发编程实战 done 02.14
    • 深入理解 Java 虚拟机 part 04.01
    • Java 性能优化 21 讲 done 12.01
  • 六本技术扩展书籍/专题
    • 图解密码技术 第三版 done 10.08
    • Linux 内核设计与实现(原书第三版)
    • 现代编译原理
    • 数据密集型应用系统设计
    • MySQL 实战 45 讲 part 08.26
    • Wireshark 数据包分析实战(第 3 版) done 2020.12.13
    • 实现领域驱动设计 part
  • 六本人文社科书籍
    • 精进 done 02.11
    • 我的改变:个人现代化 40 年 done 09.27
    • 高效 PDCA 工作术 done 09.16
    • 薄世宁医学通识讲义 done 10.20
    • 这里是中国 done 10.05
    • 活着 余华 done 12.10
    • 论中国 基辛格 done
    • 美国陷阱 done
  • 整理输出两篇高质量博客文章

丹东是中国的一个边境小城市,在东三省的辽宁,与朝鲜隔鸭绿江相望。丹东春秋去会比较好,还有点景色可以看看,冬天的话来泡温泉吧,还可以顺便来一趟朝鲜游,带着护照来就好了。可惜的是因为一些原因无法泡温泉和去趟朝鲜。所以只是趁着年末逃离下北京,换个环境放松一下,其它的不重要了。

丹东是个有山有水有美食的地方,来这边可以沿着鸭绿江散步,好奇的望望对面神秘的社会主义同僚。去趟鸭绿江美术馆、抗美援朝纪念馆之类的地方看看当地的人文。鸭绿江断桥是一定要去的,走上去一点点感受下当年的气息和脚下的滚滚江水,一座见证历史的铁桥。丹东的🍓很出名,冬天来了可以尝尝,味道还是很甜的。还有当地著名的全州拌饭馆,超级好吃绝对不是吹的,还有有名参鸡汤,夜里的烧烤。。。想想又饿了。

这里的夜景也还不错,丹东这个城市挺小的,发展还不错,赶上季节好可以来这里吃海鲜~