傻曾🍍

Stay foolish, stay hungry.


  • 首页

  • 关于

  • 分类

  • 归档

  • 标签

  • 搜索

Java并发篇(3)彻底掌握volatile关键字

发表于 2020-04-24 | 分类于 并发与多线程
字数统计 3,408字 | 阅读时长 13分钟

1. 回顾Java内存模型

在上一篇中详细介绍了Java内存模型及happens-before原则,如果还没有阅读过该文章的读者建议先阅读上一篇文章,对本文的掌握会有很大的帮助:Java并发篇(2)解读JMM内存模型

1.1 主内存与工作内存

在上一篇文章中我们已经知道线程是通过主内存去进行线程间的隐式通信的,而线程对共享变量的写操作在工作内存中完成,由JMM控制共享变量由工作内存写回到主内存的时机。

JMM提供了一个保证内存可见性的原则:happens-before原则。这个原则可以保证线程对共享变量的写操作对其它线程可见。如果在多线程环境下需要满足happens-before原则,就必须对共享变量添加某种特定的读写规则,否则会导致多线程环境下对共享变量的操作无法对其它线程可见,造成缓存不一致的现象。

1.2 如何保证操作可见性

如下图所示,在JMM中定义了8种原子操作保证线程的操作具有内存可见性。

  1. lock - read - load - use 表示一个线程需要读取并使用共享变量的过程。

  2. assign - store - write - unlock 表示一个线程需要写入共享变量的过程。

这两个读-写操作都是内存可见的,如果破坏了这些顺序,那么就无法保证内存可见性。关于这8个原子操作,不展开细说,枯燥的概念会让人打瞌睡,我们可以这样理解:

  1. 线程读共享变量时必须获取变量的锁,从主内存中获取共享变量的值并加载到工作内存中使用;
  2. 线程写共享变量时必须事先获取共享变量的锁,更新工作内存中的共享变量值后立即写入到主内存,然后释放该变量的锁。

我们不需要记住这8种原子操作的顺序,只要满足happens-before原则,就必定满足上图的情况。换言之,我们可以通过happens-before原则判断内存访问操作是否是线程间可见。

阅读全文 »

Java并发篇(2)解读JMM内存模型

发表于 2020-04-22 | 分类于 并发与多线程
字数统计 2,719字 | 阅读时长 10分钟

Java并发篇(2)解读JMM内存模型

JMM内存模型概述

在了解Java虚拟机的内存模型之前,我们要先了解为什么会出现内存模型这一概念。在物理计算机中,并发地让计算机执行任务也会出现并发问题。由于CPU与主内存之间的运算速度差距太大,所以不得不在它们之间添加一层高速缓存作为缓冲,高速缓存的读写速度接近于处理器,CPU对数据的操作都在缓存上执行,操作结束后再从高速缓存同步回主存中,所以CPU的执行效率大大提升。但是与此同时出现的另外一个问题是如何保证缓存一致性。计算机共享主内存数据,如果多个高速缓存同时往主内存中写入数据,以哪一个缓存为标准是一个很重要的问题,为了解决这个问题,在读写缓存时需要遵循一些特定的协议,保证缓存数据的一致性。同样,为了解决Java中并发产生的缓存一致性问题,模仿计算机的解决方法,产生了JMM内存模型。

物理计算机的内存访问模型:

Java中的内存访问模型:

上面两幅图是非常类似的,所以类比性非常地强,Java内存模型解决并发问题的思想本质上是依照计算机解决并发问题的思想是一致的。

JMM内存模型的区域剖析

JMM内存模型的区域分为主内存和工作内存,JMM解决了Java应用线程并发执行时出现的并发问题:1. 线程之间如何通信;2. 线程之间如何完成同步。通信是指线程之间以哪一种机制交换信息,主要有两种:共享内存和消息传递。JMM是通过读-写主内存中的共享变量进行通信的,所以理解JMM是对正确并发编程的必经之路。

阅读全文 »

Java并发篇(1)入坑并发编程的正确姿势

发表于 2020-04-21 | 分类于 并发与多线程
字数统计 1,586字 | 阅读时长 6分钟

Java并发篇(1)入坑并发编程的正确姿势

为什么要使用并发编程

随着当今CPU的高速发展,4核、8核甚至16核CPU已经面世了。在以往单核CPU的时代,每一个线程只能争抢一个CPU去获取运行的权利。在多核CPU的场景下,一个线程已经无法充分地利用多个CPU了,再者,数字化时代更加加剧了用户对应用的性能需求,传统的单线程应用已经逐渐被淘汰了,通过多线程并发执行的形式可以将CPU的计算能力发挥到极限,这是为什么需要学习并发编程的一个重要原因。

并发编程也有缺点

频繁的上下文切换

即使单核处理器也支持多线程执行代码,CPU通过给每个线程分配时间片来实现多线程并发执行。当任务A时间片执行完以后会切换到下一个任务,此时需要保存当前任务A的状态,在下一次再次轮到任务A执行的时候需要恢复这个状态,这样的一次保存和恢复称为上下文切换。频繁的上下文切换会耗费系统大量资源!

减少上下文切换的方法有

  1. 无锁并发编程:可以参照concurrentHashMap锁分段的思想,每个桶都对应一个数据段,让不同的线程处理不同段的数据,这样在多线程竞争的条件下,可以减少上下文切换的时间。
  2. CAS算法:Java的Atomic包使用CAS算法来更新数据,不需要加锁。
  3. 使用最少线程:避免创建不必要的线程,以免造成大量线程处于等待状态。
  4. 协程:在单线程里实现多线程的调度,并在单线程里维持多个任务之间的切换。
阅读全文 »

Java并发篇-Executor框架全面扫盲

发表于 2020-04-20 | 分类于 并发与多线程
字数统计 2,542字 | 阅读时长 10分钟

Java并发篇-全面解析Executor框架

Executor大家族成员

成员分为四个部分:任务、任务执行、任务执行结果以及任务执行工具类

任务:实现Callable接口或Runnable接口

任务执行部分:ThreadPoolExecutor以及ScheduledThreadPoolExecutor

任务执行结果:Future接口以及FutureTask实现类

任务执行工厂类:Executors

任务执行框架

ThreadPoolExecutor(核心)

ThreadPoolExecutor通常使用Executors进行创建,其内部含有三种线程池:

  1. FixedThreadPool:含有固定线程数的线程池。

  2. SingleThreadExecutor:单线程的线程池,需要保证任务顺序执行时采用。

  3. CachedThreadPool:大小无界的线程池,只要需要线程就可以一直创建线程。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor通常使用Executors进行创建,其内部含有两种线程池:

  1. ScheduledThreadPoolExecutor:含有固定线程数的定时任务线程池
  2. SingleThreadScheduledExecutor:只包含一个线程数的定时任务线程池,需要保证任务顺序执行时采用。
阅读全文 »

Java并发篇-Java中的线程池原理和实现

发表于 2020-04-19 | 分类于 并发与多线程
字数统计 1,830字 | 阅读时长 7分钟

Java中的线程池

线程池的实现原理

线程池执行任务的主要流程

线程池的主要处理流程

主要处理流程:

(1)判断核心线程池是否已满,如果还没满则创建线程执行任务,否则进入下一步

(2)判断工作队列是否已满,如果没满则将该任务放入工作队列等待线程来执行,否则进入下一步

(3)判断线程池中的线程是否都处于工作状态,如果不是则新创建一个线程来执行任务,没有处于工作状态的线程被淘汰,否则按照饱和策略处理该任务。

阅读全文 »

分布式架构-从单机应用走向微服务的思想及技术原理

发表于 2020-04-12 | 分类于 架构
字数统计 0字 | 阅读时长 1分钟

Java内存模型与线程

发表于 2020-04-10 | 分类于 JVM
字数统计 726字 | 阅读时长 3分钟

Java内存模型与线程

Java内存模型

上图的8个操作都是原子性的

1.Java内存模型规定所有变量(类变量和实例变量)都需要保存在主内存中

2.每条线程都有自己的工作内存,工作内存中保存了对主内存某些变量的副本,线程只能对自己的工作内存中的变量副本进行读取或写入操作,不允许直接访问主内存。

3.不同线程之间无法访问对方的工作内存中的变量,线程间变量的通信通过主内存进行。

4.只要按照上述的8条原子操作的顺序对变量进行写入或读取,那么在并发环境下每个变量都是线程安全的。

阅读全文 »

瓦解JVM-双亲委派模型

发表于 2020-04-09 | 分类于 JVM
字数统计 2,564字 | 阅读时长 11分钟

必须了解的三种类加载器

启动类加载器(Bootstrap ClassLoader)

负责加载<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径存放的,能够被虚拟机所识别的类库加载到虚拟机的内存中,这个类加载器的底层是由C++实现的,是虚拟机当中的一部分,其它类加载器都是由Java实现的,独立于虚拟机以外,全部继承自java.lang.ClassLoader抽象类。

在启动类加载器执行时,会加载一个很重要的类:sun.misc.Launcher,这个类里面含有两个静态内部类:

  1. ExtClassLoader扩展类加载器
  2. AppClassLoader应用程序加载器

阅读全文 »

瓦解JVM-一张张图带你看穿类加载机制

发表于 2020-04-08 | 分类于 JVM
字数统计 3,434字 | 阅读时长 12分钟

一张张图带你看穿类加载机制

开篇概述:Java文件在编译时转换为字节码文件,字节码文件就是对一个类的描述,Java虚拟机把Class文件加载到内存,并且经过验证、准备、解析和初始化,最终形成可以被JVM直接使用的Java类型,这就是类加载机制。下面就是对类加载机制各个过程的详细分析。

类的生命周期

类的生命周期

类的生命周期分为7个阶段,在图中我已经标注了每个阶段对类所做的主要事情,你请拿好,如果这张图能帮助到你,你的点赞是对我最大的鼓励和支持!(跑远了哈哈哈)其中验证、准备、解析三个部分统称为连接,下面我就会对每一个部分做出通俗易懂的解释,用最友好的图示来告诉你,这一个阶段JVM到底做了什么~

阅读全文 »

三分钟Java基础-重载与覆盖的区别

发表于 2020-04-08 | 分类于 Java基础
字数统计 1,299字 | 阅读时长 5分钟

面试官再问你重载和覆盖有什么区别,把这篇扔给他

如果面试官再问你重载和覆盖有什么关系?你一定要回答它,这两个概念就是名字比较像,思想是截然不同的!下面我会从实际代码来给你说明重载和覆盖的区别,并向你解释为什么会这样约束。

我们假定有三个类:Animal、Person、Dog,Dog覆盖了Animal的抽象方法,Person覆盖了Animal的抽象方法并重载了eat()方法,然后我们接着往下分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class Animal {
//动物进食方法
protected abstract void eat();
}

public class Dog extends Animal {
@Override
protected void eat() {
System.out.println("我是小狗,我现在正在吃你们撒的狗粮······");
}
}

public class People extends Animal {
@Override
protected void eat(){System.out.println("我是人类,我正在吃牛排······");}
//重载eat()方法
protected void eat(String name){
System.out.println("我是人类,我可以吃任何东西,现在正在吃"+name+"······");
}
}

覆盖父类方法的访问权限修饰符会在稍后说明,无需着急,我们先来看看Dog类和People类的eat()方法

阅读全文 »
123…9
傻曾

傻曾

爱学习,爱生活,不停下前进的脚步!

86 日志
17 分类
15 标签
GitHub CSDN QQ 微信

Tag Cloud

  • Hexo1
  • JVM6
  • Java基础8
  • MySQL11
  • SpringBoot3
  • leetcode2
  • 剑指Offer1
  • 并发与多线程10
  • 操作系统2
  • 数据库1
  • 框架3
  • 源码分析3
  • 算法与数据结构23
  • 计算机网络7
  • 软件测试2
© 2020 傻曾
本站总访问量 次 | 有人看过我的博客啦
载入天数... 载入时分秒...
0%