0%

前言

不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能。

首先来看看为什么会出现这个关键字。

内存可见性

由于 Java 内存模型(JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。

线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。

这里所提到的主内存可以简单认为是堆内存,而工作内存则可以认为是栈内存

如下图所示:

所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。

显然这肯定是会出问题的,因此 volatile 的作用出现了:

当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。

Read more »

众所周知 HashMap 是一个无序的 Map,因为每次根据 keyhashcode 映射到 Entry 数组上,所以遍历出来的顺序并不是写入的顺序。

因此 JDK 推出一个基于 HashMap 但具有顺序的 LinkedHashMap 来解决有排序需求的场景。

它的底层是继承于 HashMap 实现的,由一个双向链表所构成。

LinkedHashMap 的排序方式有两种:

  • 根据写入顺序排序。
  • 根据访问顺序排序。

其中根据访问顺序排序时,每次 get 都会将访问的值移动到链表末尾,这样重复操作就能的到一个按照访问顺序排序的链表。

Read more »

使用 synchronize 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。

ReentrantLock 就是一个普通的类,它是基于 AQS(AbstractQueuedSynchronizer)来实现的。

是一个重入锁:一个线程获得了锁之后仍然可以反复的加锁,不会出现自己阻塞自己的情况。

AQSJava 并发包里实现锁、同步的一个重要的基础框架。

锁类型

ReentrantLock 分为公平锁非公平锁,可以通过构造方法来指定具体类型:

1
2
3
4
5
6
7
8
9
//默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}

//公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

默认一般使用非公平锁,它的效率和吞吐量都比公平锁高的多(后面会分析具体原因)。

Read more »

创建对象

JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被加载过了,如果没有的话则要进行一次类加载。

接着就是分配内存了,通常有两种方式:

  • 指针碰撞
  • 空闲列表

使用指针碰撞的前提是堆内存是完全工整的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可。

当堆中已经使用的内存和未使用的内存互相交错时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式。虚拟机会维护一个空闲的列表,用于记录哪些内存是可以进行分配的,分配时直接从可用内存中直接分配即可。

堆中的内存是否工整是有垃圾收集器来决定的,如果带有压缩功能的垃圾收集器就是采用指针碰撞的方式来进行内存分配的。

Read more »

众所周知 synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式:

  • 同步普通方法,锁的是当前对象。
  • 同步静态方法,锁的是当前 Class 对象。
  • 同步块,锁的是 () 中的对象。

实现原理:
JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。

具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。

其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。

而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。

流程图如下:

Read more »

当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题:

如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。

Hash 取模

随机放置就不说了,会带来很多问题。通常最容易想到的方案就是 hash 取模了。

可以将传入的 Key 按照 index = hash(key) % N 这样来计算出需要存放的节点。其中 hash 函数是一个将字符串转换为正整数的哈希映射方法,N 就是节点的数量。

这样可以满足数据的均匀分配,但是这个算法的容错性和扩展性都较差。

比如增加或删除了一个节点时,所有的 Key 都需要重新计算,显然这样成本较高,为此需要一个算法满足分布均匀同时也要有良好的容错性和拓展性。

Read more »

前言

看过之前SBC系列的小伙伴应该都可以搭建一个高可用、分布式的微服务了。 目前的结构图应该如下所示:

各个微服务之间都不存在单点,并且都注册于 Eureka ,基于此进行服务的注册于发现,再通过 Ribbon 进行服务调用,并具有客户端负载功能。

一切看起来都比较美好,但这里却忘了一个重要的细节:

当我们需要对外提供服务时怎么处理?

这当然也能实现,无非就是将我们具体的微服务地址加端口暴露出去即可。

那又如何来实现负载呢?

简单!可以通过 Nginx F5 之类的工具进行负载。

但是如果系统庞大,服务拆分的足够多那又有谁来维护这些路由关系呢?

当然这是运维的活,不过这时候运维可能就要发飙了!

并且还有一系列的问题:

  • 服务调用之间的一些鉴权、签名校验怎么做?
  • 由于服务端地址较多,客户端请求难以维护。

针对于这一些问题 SpringCloud 全家桶自然也有对应的解决方案: Zuul
当我们系统整合 Zuul 网关之后架构图应该如下所示:

Read more »

原文链接

1 在 GitHub.com 编辑代码

我将从我认为大家都知道的一件事情开始(尽管我是直到一周前才知道)。

当你在 GitHub 查看文件时(任何文本文件,任何仓库中),右上角会有一个小铅笔图标,点击它就可以编辑文件了。完成之后点击 Propose file change 按钮 GitHub 将会自动帮你 fork 该项目并且创建一个 pull request

很厉害吧!他自动帮你 fork 了该 repo。

不再需要 fork , pull ,本地编辑再 push 以及创建一个 PR 这样的流程了。

这非常适合修复编写代码中出现的拼写错误和修正一个不太理想的想法。

2 粘贴图片

你不仅仅受限于输入文本和描述问题,你知道你可以直接从粘贴板中粘贴图片吗?当你粘贴时,你会看到图片已经被上传了(毫无疑问被上传到云端)之后会变成 Markdown 语法来显示图片。

3 格式化代码

如果你想写一段代码,你可以三个反引号开始 —— 就像你在研究MarkDown时所学到的 —— 之后 GitHub 会试着猜测你写的语言。

但如果你写了一些类似于 Vue, Typescript, JSX 这样的语言,你可以明确指定得到正确的高亮。

注意第一行中的

1
```jsx

Read more »

前言

写这篇文章的起因是由于之前的一篇关于Kafka异常消费,当时为了解决问题不得不使用临时的方案。

总结起来归根结底还是对Kafka不熟悉导致的,加上平时工作的需要,之后就花些时间看了Kafka相关的资料。

何时使用MQ

谈到Kafka就不得不提到MQ,是属于消息队列的一种。作为一种基础中间件在互联网项目中有着大量的使用。

一种技术的产生自然是为了解决某种需求,通常来说是以下场景:

  • 需要跨进程通信:B系统需要A系统的输出作为输入参数。
  • 当A系统的输出能力远远大于B系统的处理能力。

针对于第一种情况有两种方案:

  • 使用RPC远程调用,A直接调用B。
  • 使用MQ,A发布消息到MQ,B订阅该消息。

当我们的需求是:A调用B实时响应,并且实时关心响应结果则使用RPC,这种情况就得使用同步调用。

Read more »

1

前言

看过 应用限流的朋友应该知道,限流的根本目的就是为了保障服务的高可用。

本次再借助SpringCloud中的集成的Hystrix组件来谈谈服务容错。

其实产生某项需求的原因都是为了解决某个需求。当我们将应用进行分布式模块部署之后,各个模块之间通过远程调用的方式进行交互(RPC)。拿我们平时最常见的下单买商品来说,点击下单按钮的一瞬间可能会向发送的请求包含:

  • 请求订单系统创建订单。
  • 请求库存系统扣除库存。
  • 请求用户系统更新用户交易记录。

这其中的每一步都有可能因为网络、资源、服务器等原因造成延迟响应甚至是调用失败。当后面的请求源源不断的过来时延迟的资源也没有的到释放,这样的堆积很有可能把其中一个模块拖垮,其中的依赖关系又有可能把整个调用链中的应用Over最后导致整个系统不可能。这样就会产生一种现象:雪崩效应

之前讲到的限流也能起到一定的保护作用,但还远远不够。我们需要从各个方面来保障服务的高可用。

比如:

  • 超时重试。
  • 断路器模式。
  • 服务降级。
    等各个方面来保障。
Read more »