【重难点】【Java基础 01】一致性哈希算法、sleep() 和wait() 的区别、强软弱虚引用

文章目录

  • 【重难点】【Java基础 01】一致性哈希算法、sleep() 和wait() 的区别、强软弱虚引用
  • 一、一致性哈希算法
    • 1.简介
    • 2.场景
    • 3.原理
  • 二、sleep() 和wait() 的区别
    • 1.对比
  • 三、强软弱虚引用
    • 1.概念
  • 参考链接

一、一致性哈希算法

1.简介

一致性哈希算法在 1997 年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表 ( Distributed Hash Table,DHT) 中存在的动态伸缩等问题

2.场景

数据库迁移

例如,一个公司半年前预估未来两年内,总订单量可达一亿条左右

根据《阿里巴巴Java开发手册》
【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表

按照 MySQL 每张表存储 500 万条记录,暂时不必分库,单库 30 个分表时比较合适的水平分表发方案

于是设计了这样的分表逻辑:

  1. 订单表创建单库 30 个分表
  2. 对用户 ID 和 30 进行取模,取模结果决定了记录存在第几个分表
  3. 查询时需要以用户 ID 作为条件,根据取模结果确定查询哪一个分表

但是才过去半年,现在项目流量超出预估,订单量已经接近 1 亿了,这样当初设计的 30 个分表就不够用了

  • 直接增加分表会导致原先的哈希规则被打乱
  • 全量数据迁移可以解决问题,但是代价太大

我们可以利用一致性哈希算法,把用户 id 散列到各个 sharding 节点,这样就可以保证添加和删除节点数据迁移影响较小

Redis 缓存服务器不够用或者其中一台出故障

如果我们对 Redis 集群使用 Hash,那么无论是增加服务器还是减少服务器,所有缓存的位置都需要发生改变

而且当服务器数量发生改变时,所有缓存在一定时间内是失效的,当应用无法从缓存中获取数据时,则会向后端数据库请求数据,可能会发生缓存雪崩

一致性哈希算法可以有效解决分布式存储结构下动态增加和删除节点所带来的问题

考虑到分布式系统每个节点都有可能失效,并且新的节点很可能会是动态地增加进来。一致性哈希算法能保证在系统的节点数目发生变化时,系统仍然能够对外提供良好的服务

此外,一致性 Hash 是一种特殊的Hash算法,由于其均衡性、持久性的映射特点,被广泛的应用于负载均衡领域,如 nginx 和memcached 都采用了一致性 Hash 来作为集群负载均衡的方案

3.原理

  1. 一致性 Hash 算法将整个哈希值空间组织成一个虚拟的圆环
  2. 假设某哈希函数 hash() 的值空间为 0-2^32-1
  3. hash(A) % 2^32 求出服务器所在位置,图中 A、B、C 是服务器
  4. hash(a.jpg)% 2^32 求出数据在圆环上的落点,顺时针前进,把数据存放在最先遇到的服务器,图中 a.jpg 、b.jpg 、c.jpg 代指要存放的数据

增加节点或者宕机的情形

假如我们插入的新节点是 D,那么节点 C 到 D 之间这一小部分数据是访问不到的,但是节点 D 到 C 之间的大部分数据是能够正常访问的。服务器宕机也是一样的原理

但是仅仅这样会导致以下两个问题

如果节点的数量很少,而hash环空间很大(一般是 0 ~ 2^32),直接进行一致性hash上去,大部分情况下节点在环上的位置会很不均匀,挤在某个很小的区域。最终对分布式缓存造成的影响就是,集群的每个实例上储存的缓存数据量不一致,会发生严重的数据倾斜

如果每个节点在环上只有一个节点,那么可以想象,当某一集群从环中消失时,它原本所负责的任务将全部交由顺时针方向下一个集群处理。例如,当 A 退出时,它原本所负责的缓存将全部交给 B 处理。这就意味着 B 的访问压力会瞬间增大。设想一下,如果 B 因为压力过大而崩溃,那么更大的压力又会向 C 压过去,最终服务压力就像滚雪球一样越滚越大,最终导致雪崩

因此,引入虚拟节点

引入虚拟节点机制,即对每一个服务节点计算多个哈希,那么每个服务器在圆环上的分布就相对均匀了

二、sleep() 和wait() 的区别

1.对比

Java 中的多线程是一种抢占式的机制,而不是分时机制。线程主要有以下几种状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。抢占式机制指的是有多个线程处于就绪状态,但是只有一个线程在运行

当有多个线程访问共享数据的时候,就需要对线程进行同步。每个对象都有一个锁来控制同步访问,synchronized 关键字可以和对象的锁交互,来实现线程的同步

共同点是 sleep() 和 wait() 都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。sleep() 和 wait() 都可以通过 interrupt() 方法来打断线程的暂停状态,从而使线程立刻抛出 InterruptedException

如果线程 A 希望立即结束线程 B,则可以对线程 B 对应的 Thread 实例调用 interrupt 方法。如果此刻线程 B 正在wait/sleep/join,则线程 B 会立刻抛出 InterruptedException,在 catch() {} 中直接 return 即可安全地结束线程
需要注意的是,InterruptedException 是线程自己从内部抛出的,并不是 interrupt() 方法抛出的。对某一线程调用 interrupt() 时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出 InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join() 后,就会立刻抛出 InterruptedException

1、sleep() 可以在 synchronized 块或方法之外的地方调用,wait() 必须在 synchronized 的块或方法下使用

2、sleep() 不释放锁,而 wait() 释放锁

这是由于sleep() 是 Thread 类的方法,因此它不能改变对象的锁。所以在一个被 synchronize 修饰的方法中调用 sleep() 时,线程虽然休眠了,但是对象的锁没有被释放,其他线程仍然无法访问这个对象。而 wait() 则会在线程休眠的同时释放锁,这样做的好处是它不影响其它线程对 object 进行操作,其他线程可以访问该对象。当一个线程执行到 wait() 方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的锁。当它被一个 notify() 方法唤醒时,等待池中的线程就被放到了锁池中。该线程从锁池中获得机锁,然后回到 wait() 前的中断现场

一个线程结束的标志是:run() 方法结束,一个锁被释放的标志是 synchronized 块或方法结束

3、sleep() 是 Thread 类的方法,wait() 是顶级父类 Object 的方法

4、sleep() 必须捕获异常,而wait(),notify() 和 notifyAll() 不需要捕获异常

5、不调用 interrupt() 方法,sleep() 无法被中途唤醒,wait() 可以随时被 notify() 或者 notifyAll() 唤醒

sleep() wait()
同步 可以在同步块或方法之外调用 只能在同步方法块或同步方法中调用,否则抛出 IllegalMonitorStateException
作用对象 作用于当前线程 作用于对象本身
释放锁资源
唤醒条件 超时或者调用 interrupt() 方法 其他线程调用对象的 notify() 或者 notifyAll() 方法
方法属性 sleep() 是静态方法 wait() 是实例方法

三、强软弱虚引用

1.概念

Java 内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看是否有引用指向该对象

Java 对象的引用包括:

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

Java 中提供这四种引用类型主要有两个目的:

  • 可以让程序员通过代码的方式决定某些对象的生命周期
  • 有利于 JVM 进行垃圾回收

Reference 继承自 Object,有 SoftReference、WeakReference、PhantomReference 三个直接子类

三个子类的构造方法中涉及到 ReferenceQueue 和 Referent

Referent:被引用的对象

ReferenceQueue
以弱引用为例

回收前获取引用队列中的对象null
回收后获取引用队列中的对象java.lang.ref.WeakReference@610455d6

这个类的作用就是对象被垃圾回收后会存放到引用队列中

强引用(StrongReference)

强引用是使用最普遍的引用。只要某个对象有强引用与之关联,JVM 必定不会回收这个对象,即使是在内存不足的情况下,JVM 宁愿抛出 OutOfMemory 错误也不会回收这类对象。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为 null,这样一来,JVM 就会在合适的时间回收该对象

软引用(SoftReference)

软引用是用来描述一些有用但并不是必需的对象,在 Java 中用 java.lang.ref.SoftReference 类来表示,只有在内存不足的时候 JVM 才会回收该对象。因此,这一点可以很好地用来解决 OOM 的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等

String str=new String("abc");                                     // 强引用
SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用

弱引用(WeakReference)

只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中

 // 创建一个引用队列
ReferenceQueue<String> rq = new ReferenceQueue<String>();
// 实现一个弱引用,将强引用类型hello和是实例化的rq放到弱引用实现里面
WeakReference<String> sr = new WeakReference<String>(new String("hello"), rq);

虚引用(PhantomReference)

”虚引用“ 顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收机制回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动,这也是虚引用的主要作用。并且,虚引用在加入与之关联的引入队列时不会自动清除没通过虚引用可访问的对象将已知保持到所有这样的引用被清除或者自身变得不可访问

特别注意,在实际程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生

一个测试案例

package com.example;import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;public class ReferenceTest {public static void main(String[] args) {//testSoftReference();//testSoftReferenceWithQueue();//testWeakReference();//testWeakReferenceWithQueue();//testPhantomReference();}/*** 虚引用*/private static void testPhantomReference() {Person person = new Person();System.out.println("person对象为" + person);ReferenceQueue<Person> queue = new ReferenceQueue<>();PhantomReference<Person> pr = new PhantomReference<>(person, queue);System.out.println("pr对象为" + pr);System.out.println("pr.get()=" + pr.get());person = null;System.out.println("queue item:" + queue.poll());System.gc();try {//确保垃圾回收线程能够执行Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("queue item: " + queue.poll());}/*** 弱引用*/private static void testWeakReferenceWithQueue() {Person person = new Person();System.out.println("person对象为" + person);ReferenceQueue<Person> queue = new ReferenceQueue<>();WeakReference<Person> wr = new WeakReference<Person>(person, queue);System.out.println("wr对象为" + wr);person = null;if (wr.get() == null) {System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + wr.get());}System.out.println("Whether or not this reference has been enqueued: " + wr.isEnqueued());System.out.println("queue item:" + queue.poll());System.gc();if (wr.get() == null) {//仅是表明其指示的对象已经进入垃圾回收流程,此时对象不一定已经被垃圾回收。只有确认被垃圾回收后,如果有ReferenceQueue,其引用才会被放置于ReferenceQueue中System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + wr.get());}try {//确保垃圾回收线程能够执行Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Whether or not this reference has been enqueued: " + wr.isEnqueued());System.out.println("queue item:" + queue.poll());}/*** 弱引用*/private static void testWeakReference() {Person person = new Person();System.out.println("person对象为" + person);WeakReference<Person> wr = new WeakReference<>(person);person = null;//被GC后,之前new出的erson对象会立即被回收,进入GC流程if (wr.get() == null) {System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + wr.get());}System.gc();if (wr.get() == null) {System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + wr.get());}}/*** 软引用*/private static void testSoftReferenceWithQueue() {Person person = new Person();System.out.println("person对象为" + person);ReferenceQueue<Person> queue = new ReferenceQueue<>();SoftReference<Person> sr = new SoftReference<Person>(person, queue);person = null;//之前new出的Person对象不会立即被回收,除非JVM需要内存(OOM之前)if (sr.get() == null) {System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + sr.get());}System.out.println("加入ReferenceQueue的对象为" + queue.poll());System.gc();if (sr.get() == null) {System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + sr.get());}System.out.println("加入ReferenceQueue的对象为" + queue.poll());}/*** 软引用*/private static void testSoftReference() {Person person = new Person();System.out.println("person对象为" + person);SoftReference<Person> sr = new SoftReference<Person>(person);person = null;//之前new出的Person对象不会立即被回收,除非JVM需要内存(OOM之前)if (sr.get() == null) {System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + sr.get());}System.gc();if (sr.get() == null) {System.out.println("person对象进入GC流程");} else {System.out.println("person对象尚未被回收" + sr.get());}}static class Person {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("in Person finalize");}}}

参考链接

强烈建议结合以下链接理解!

7分钟讲解一致性哈希
分布式算法(一致性Hash算法)
负载均衡中的一致性Hash算法详解
利用一致性哈希水平拆分MySql单表

例子很生动,就是味道有点大
sleep和wait的区别
sleep和wait的区别
JAVA—sleep()和wait()的区别

11分钟讲解强软弱虚引用
Java关于强引用,软引用,弱引用和虚引用的区别与用法
虚引用所指向的对象到底什么时候被回收?
Java Reference(SoftReference、WeakReference、PhantomReference)的使用
强软弱虚引用,只有体会过了,才能记住

【重难点】【Java基础 01】一致性哈希算法、sleep() 和wait() 的区别、强软弱虚引用相关推荐

  1. java虚引用作用_深入理解Java中的引用(二)——强软弱虚引用

    深入理解Java中的引用(二)--强软弱虚引用 在上一篇文章中介绍了Java的Reference类,本篇文章介绍他的四个子类:强引用.软引用.弱引用.虚引用. 强引用(StrongReference) ...

  2. Java中的强软弱虚引用《对Java的分析总结三》

    <对Java的分析总结>-Java中的强软弱虚引用 强引用 StrongReference 软引用 SoftReference 弱引用 WeakReference 虚引用 PlantomR ...

  3. 面试时遇到一致性哈希算法这样回答会让面试官眼前一亮

    [CSDN 编者按]很多人都知道什么是哈希函数,在后端面试和开发中会遇到"一致性哈希",那什么是一致性哈希呢,当面试官问到你又该如何给出漂亮的回答. 作者 | 丁威       责 ...

  4. java 取绝对值_Java实现一致性哈希算法,并搭建环境测试其负载均衡特性

    实现负载均衡是后端领域一个重要的话题,一致性哈希算法是实现服务器负载均衡的方法之一,你很可能已在一些远程服务框架中使用过它.下面我们尝试一下自己实现一致性哈希算法. 一. 简述一致性哈希算法 这里不详 ...

  5. Java 基础-01 Java语言入门

    文章目录 Java 基础-01 Java语言入门 1.计算机基本概念 1.1 计算机概述 1.2 计算机组成 1.3 CPU.内存与硬盘 2.软件基本概念 2.1 软件概述 2.2 人机交互方式 2. ...

  6. 一致性哈希算法学习及JAVA代码实现分析

    1,对于待存储的海量数据,如何将它们分配到各个机器中去?---数据分片与路由 当数据量很大时,通过改善单机硬件资源的纵向扩充方式来存储数据变得越来越不适用,而通过增加机器数目来获得水平横向扩展的方式则 ...

  7. 一致性哈希算法与Java实现

    来源:http://blog.csdn.net/wuhuan_wp/article/details/7010071 一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具 ...

  8. php byte stringbuffer,重拾java基础(十三):String姐妹StringBuffer、StringBuilder总结

    重拾java基础(十三):String姐妹StringBuffer.StringBuilder总结 一.StringBuffer类概述buffer:缓冲 2. 字符串缓冲区,跟String非常相似,都 ...

  9. java一致性hash api_一致性哈希算法学习及JAVA代码实现分析

    戳上面的蓝字关注我们哦! 本文作者:hapjin 欢迎点击下方阅读原文 1,对于待存储的海量数据,如何将它们分配到各个机器中去?---数据分片与路由 当数据量很大时,通过改善单机硬件资源的纵向扩充方式 ...

最新文章

  1. TinyMind 汉字书法识别竞赛开启总决赛啦!!
  2. dubbo-go v1.5.6来喽!
  3. Java 集合时间复杂度
  4. 快速学会开发微信小程序
  5. CF 46D Parking Lot
  6. Android 屏幕适配攻略(一)
  7. 21天Jmeter打卡Day14 监听器之查看结果树+断言结果+聚合报告+图形结果+表格查看结果
  8. Android Studio升级中的“未找到默认活动”
  9. SpringCloud学习
  10. Android studio常用设置和快捷键
  11. bzoj 4816: 洛谷 P3704: [SDOI2017]数字表格
  12. 罗振宇4小时跨年演讲精华版:大环境里的7个行动策略
  13. MVC、MVP与MVT
  14. 计算机共享打印机怎么弄,电脑共享打印机怎么设置,手把手教你设置共享打印机...
  15. 关于cpu-z,everest,IntelCoreSeries的一些认识
  16. Qt之自定义属性Q_PROPERTY
  17. 从程序员到项目经理(二十一):谁都需要成就感
  18. 微信服务商如何申请?
  19. python中123+5.0的执行结果_123+5.0的执行结果为()_学小易找答案
  20. CorelDRAW VBA - 打开文件(另存为)对话框

热门文章

  1. [BZOJ1059][ZJOI2007]矩阵游戏
  2. 函数式接口 lambda表达式 方法引用
  3. npm 报错 Module build failed: Error: No PostCSS Config found in:
  4. 省选专练【POI2015】Podzial naszyjnika
  5. MediaPlayer播放音频,也可以播放视频
  6. jquery自定义banner图滚动插件---(解决最后一张图片倒回第一张图片的bug)
  7. 融会贯通——最常用的面向对象设计原则“合成复用原则”
  8. dijkstra 最短路算法
  9. 《JavaScript语言精粹》笔记
  10. OAuth 授权timestamp refused问题