概述

用Java来开发多线程程序变得越来越常见,虽然Java提供了并发包来简化多线程程序的编写,但是我们有必要深入研究一下,才能更好的掌握这块知识。

本文主要对Java提供的底层原语synchronized和volatile进行分析,看看他们究竟干了什么,以及怎么样才能合理的使用它们。

运算速度与IO速度的问题

现代计算机模型,待计算的数据主要存储在内存中,CPU想要对数据进行计算,就必须要经过下面的流程:从内存读取数据-->CPU计算-->把计算结果写回内存。但是,不得不承认一个事实,就是CPU与内存之间的IO速度,要比CPU的运算速度慢很多,所以这样就不能充分发挥CPU的计算能力。

为了解决这个问题,引入了高速缓存的概念,它一般位于内存与CPU之间,与CPU之间有较高的IO速度。高速缓存存放常用的数据,这样就可以大幅度提高CPU的利用率。

由缓存命中问题引出的指令重排序

上面提到了高速缓存之间的概念。既然提到高速缓存,那么就要谈到缓存命中的问题,如果缓存命中率很高,那么整体性能就会提升。所以,试想一下,有下面几行代码:

int a = 0;
a = a + 10;
int b = 0;
b = b + 5;
a = a * 2;

通常我们会认为,执行完第2条代码后,a被写入到高速缓存,然后执行对b的操作,最后再从高速缓存中读取出a,再对a做乘法计算。

但是,现代编译器都会对这种情况做优化。考虑一个问题,既然两次对a操作的指令之间,没有使用到a的指令,那么为什么不在对a做完加法后,直接再对a做个乘法呢。反正又不会影响整个代码的语义,而且还能避免高速缓存不命中的问题,万一这之间执行的代码很多,然后a被清理出了高速缓存,那么到最后执行对a的乘法时,又要从内存中读取,这样看来,很不划算,所以就出现了指令重排。就是说指令真正的执行顺序,不一定是按照代码书写的顺序执行的,可能会打乱顺序执行,但是只要不影响整段代码的语义就行了,因为它们是“好像是串行执行”的方式。

比如下面的代码,如果对a的操作真的发生在对b的操作之前,那么就改变了整段代码的语义,所以这时候就不会重排指令。

int a = 0;
a = a + 10;
int b = 0;
b = b + a;
a = a * 2;

Java的存储(内存)结构

内存中,主要分为两大块区域:

1、全局区域,所有线程共享该区域。

2、线程私有区域,存放需要使用的全局数据的副本。

存储结构:

第一层,位于CPU内部的高速缓存,为了解决CPU与内存之间IO速度和CPU执行速度之间差距太大的问题。

第二层:内存(包括全局区域和线程私有区域)。

对一个变量的操作,通常需要经过下面的这8个步骤(如果不是同步操作,没有1和8)。见下图的蓝色字。

多线程要考虑的问题

1、内存一致性

某个变量,它在主内存中的值,应该和在线程工作内存中的值是一致的。

2、内存可见性

某个变量,如果一个线程对它进行修改,那么其他线程应该能立即看到它的变化。

3、有序性

如果一个线程A依赖另一个线程B的执行结果,那么在线程B看来是串行执行的指令(其实可能经过了指令重排),在线程A看来,就是一个错误的执行顺序。比如下面的情况:

Thread 1 Thread 2
x = 1; int r1 = y;
y = 2; int r2 = x;

有可能出现 r1 = 2 、r2 = 0 的情况,因为Thread1可能会进行指令重排,所以对于Thread2来说,就发生了错误。

synchronized工作原理

synchronized关键字,通过第1步,对主内存中变量加锁的操作(lock),获得了该变量的使用权,随后,其他线程没有访问这个被加锁变量的权利,一直到该线程使用完成,然后解锁该变量(unlock),之后其他线程才能访问该变量,当然也能看到该变量最新的结果。

由于synchronized以一种让上述8个步骤原子执行的方式工作,所以,它解决了内存一致性的问题,内存可见性的问题、有序性的问题。

使用场景:所有在有多线程共享数据的地方,都可以使用,简单粗暴,但是会引降低性能。

volatile工作原理

volatile关键字,通俗点来说,就是对一个变量的操作,2(read) 、3(load)、 4(use)这3个操作必须是原子的,而
5(assign)、6(store)、7(write)这3个操作也必须是原子的。

那么我们来看看,它能解决什么问题,因为简单来说就是,读(2 3 4)和写(5 6
7)操作分别都是原子的,相当于CPU每次都是直接和内存交互,所以高速缓存就变得无效了。而高速缓存变得无效,那么基本上,也就没有指令重排了。而由于每次改动都会直接写到内存,每次使用都从内存读取新的数据,所以也就满足了内存可见性。但是由于在读和写之间,其他线程也可以进行读和写,那么还是会出现内存一致性问题的。所以它解决了内存可见性、有序性、但是不能解决内存可见性。

        使用场景:由于volatile的特性,主要可以使用在下面的场景

1、不依赖于变量之前的状态的,比如一个 volatile boolean 来在一个线程中控制另一个线程的运行。

2、禁止指令重排,比如一个线程依赖于另一个线程真正的顺序执行结果,而不是语义上的顺序执行(其实是经过指令重排的)。

Java多线程之synchronized和volatile的更多相关文章

  1. JAVA多线程之synchronized和volatile实例讲解

    在多线程中,提到线程安全.线程同步,我们经常会想到两个关键字:volatile和synchronized,那么这两者有什么区别呢? 1. volatile修饰的变量具有可见性 volatile是变量修 ...

  2. (二)java多线程之synchronized

    本人邮箱: kco1989@qq.com 欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco github: https://github.com/kco198 ...

  3. JAVA多线程之Synchronized关键字--对象锁的特点

    一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的一些知识点. 所谓对象锁,就是就是synchronized 给某个对象 加锁.关于 对象锁 可参考:这篇文章 二,分析 s ...

  4. JAVA多线程之Synchronized、wait、notify实例讲解

    一.Synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争 ...

  5. Java多线程之synchronized及其优化

    Synchronized和同步阻塞synchronized是jvm提供的同步和锁机制,与之对应的是jdk层面的J.U.C提供的基于AbstractQueuedSynchronizer的并发组件.syn ...

  6. Java多线程之synchronized详解

    目录 synchronized简介 同步的原理 对象头与锁的实现 锁的优化与升级 Monitor Record 锁的对比 synchronized简介 synchronized关键字,一般称之为&qu ...

  7. Java多线程之synchronized(四)

    前面几章都是在说synchronized用于对象锁,无论是修饰方法也好修饰代码块也好,然而关键字synchronized还可以应用到static静态方法上,如果这样写,那就是对当前的*.java文件所 ...

  8. Java多线程之synchronized(三)

    在多线程访问同一个对象中的不同的synchronized方法或synchronized代码块的前提下,也就是“对象监控器”为同一个对象的时候,也就是synchronized的锁为同一把锁的时候,调用的 ...

  9. java 多线程之synchronized wait/notify解决买票问题

    一.Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); 就绪状态(Runnable):当调用线程对象的st ...

随机推荐

  1. aliyun---经过LB到后端k8s压测超时的问题

    环境:阿里云 压测主机:阿里云ECS(非LB后的主机) 压测目标:阿里云k8s自己的某个服务 k8s配置在kube-system 按照之前的ingress-nginx 配置了一个内网的ingress- ...

  2. php 的file 缓存

    PDO方式连接数据库类 <?php /** * @author 黄功延 * createTime 2018/5/28 0028 09:44 */ class Db { //私有化数据库连接数据, ...

  3. Mac将本地文件上传到Centos7(Linux)服务器上

    1.打开终端,输入命令: scp /Users/codez/Downloads/jdk-8u144-linux-x64.tar.gz root@139.224.235.xxx:/root/java/j ...

  4. Python基础知识总结笔记(四)函数

    Python基础知识总结笔记(四)函数python中的函数函数中的参数变量作用域偏函数PFA递归函数高阶函数BIFs中的高阶函数匿名函数lambda闭包Closure装饰器Decorator函数式编程 ...

  5. 【转载】structlog4j介绍

    源文章:structlog4j介绍 结构化日志对于日志的收集的作用挺大的,根据自身的业务场景,基于SLF4J实现了structlog4j. 相关引用 Gradle // 基础包 compile 'te ...

  6. Python之基础、细节

    引号的用法 单引号对 ' ' :表示字符串,可以换行 双引号对 " " :表示字符串 三引号对 ''' ''' 和 """ ""& ...

  7. 剑指offer-面试题64-求1+2+...+n-发散思维

    /* 题目: 求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C) */ /* 思路: 递归. */ #incl ...

  8. (未完成)【Android】MVP模式初见(一)

    最近在阅读郭霖大神的公众号时,分类中架构引起了我的注意. 虽然是个人开发(水平很菜的那种),但最终都要向企业正式项目开发靠近.因此接下来一段时间,主要学习一下MVP架构.Retrofit以及RxJav ...

  9. centos7安装opencv3.4.1(同样适用于最新版本4.2.0)

    安装cmake3: echo '[group_kdesig-cmake3_EPEL]name=Copr repo for cmake3_EPEL owned by @kdesigbaseurl=htt ...

  10. cf999E (强联通分量模板题)

    给出n个点m条边的有向图,问至少添加多少条边使得任何点都可以从s点出发可达 #include<bits/stdc++.h> #define forn(i, n) for (int i = ...