文档章节

java 多线程总结

侠客人生
 侠客人生
发布于 2017/07/20 12:56
字数 4075
阅读 93
收藏 1
点赞 1
评论 0

1. 多线程

1.1进程

进程是一个实体。每一个进程都有它自己的地址空间。进程是一个“执行中的程序”。

1.2线程

一个进程中可以包含若干个线程 

1.3线程与进程的区别

1.3.1地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
1.3.2通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
1.3.3调度和切换:线程上下文切换比进程上下文切换要快得多。

1.3.4在多线程OS中,进程不是一个可执行的实体。

eg: 

QQ 进程

给范冰冰聊天 线程

给普京聊天 线程

1.4线程的状态

新建态,就绪态,运行态,阻塞态,消亡态

示意图

1.5创建线程的方式

1.5.3.1实现Runnable接口,重写run方法

1.5.3.2直接继承Thread类

1.5.3.3实现Runnable接口后使用线程池ThreadPool

    eg: 企业中推荐使用该方法去创建线程,想了解具体请查看【java线程池】

           https://my.oschina.net/u/2505908/blog/1476242

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

1.5.3.4实现Callable接口

1.6多线程安全问题

1.6.1产生原因

多个线程可以利用所拥有的共享资源

1.6.2解决思想

就是将多个 [ 操作共享数据 ]的线程 代码封装起来,

当有线程在执行这些代码的时候,其他线程是不可以参与运算的,

必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

比如synchronized代码块

2. java内存模型(JMM:java memory model)

2.1硬件的效率与一致性

2.1.1高速缓存产生原因:

由于计算机的存储设备与处理器的运算能力之间有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(cache)来作为内存与处理器之间的缓冲

2.1.2读写过程

将运算需要使用到的数据复制到缓存中,让运算能快速进行,

当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。

2.1.3缓存一致性(Cache Coherence)

多个处理器运算任务都涉及同一块主存,需要一种协议可以保障数据的一致性,这类协议有MSI、MESI、MOSI及Dragon Protocol等

当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本, 会发出信号通知其他CPU将该变量的缓存行置为无效状态,

因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取

2.1.4Cpu读写示意图

 

2.1.5类比

Java虚拟机内存模型中定义的内存访问操作 与 硬件的缓存访问操作 是具有相似性

2.2 多线程类比cpu读写示意图

JMM

概念:描述了java程序中各种变量(共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节

2.2.2共享变量

概念:如果一个变量在多个线程的工作内存中都存在副本,那这个变量就是这几个线程的共享变量

即 一个变量 在 多个线程中 存在 多个副本,每个线程都是操作各自的变量副本,就可能发生副本不一致

2.2.2多线程操作共享变量规则

a. 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写

b. 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成

 

2.3内存间交互操作

注意:多线程讨论的安全问题都是针对共享变量而言,不包括局部变量

每个线程 定义的局部变量,不会被其他线程访问

2.3.1 线程读取变量过程

数据流向:从主内存拷贝到工作内存

3个操作:

lock(锁定)→ 变量标识为线程独占  →

→read(读取)→ 变量值从主内存传输到线程的工作内存中→

→load(载入)→ 把read操作从主内存中得到的变量值放入工作内存的变量副本中

2.3.2操作变量

两个操作

use(使用)→ 把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作→

→assign(赋值)→把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作

2.3.3从工作内存同步到主内存

三个操作

store(存储)→把工作内存中的变量的值传送到主内存中→

→write(写入)→把store传递过来的变量值写入主内存的变量中→

→unlock(解锁)→把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

2.3.4顺序执行规则

Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的。

如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a

大家可以想一下,工作内存同步到主内存,主内存再同步到工作内存,需要花费时间的,花费的时间越多,发成不安全的因素也就越多,也就是我们的俗话:“夜长梦多”!!!

2.3.5重排序

 

2.3.5.1概念:代码书写的顺序与实际执行的顺序不同

2.3.5.2重排序的作用:在执行程序时为了提高性能,编译器或处理器经常会对指令进行重排序。

2.3.5.3三种类型:

1. 编译器优化的重排序(编译优化)

编译器在不改变单线程程序语义前提下,可以重新安排语句的执行顺序。

2. 指令级并行的重排序(处理器优化):

现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3. 内存系统的重排序(处理器优化):

由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

从Java源代码到最终实际执行的指令序列的过程:

 

2.3.5.4内存屏障

作用:为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。

分类:LoadLoad、LoadStore、StoreLoad和StoreStore

2.3.5.5 as-if-serial

无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(java编译器、运行时和处理器都会保证java在单线程下遵循as-if-serial语义)

重排序不会给单线程带来内存可见性问题

多线程中程序交错执行时,重排序可能会造成内存可见性问题

3如何保证线程安全

3.1必须满足三个特性

原子性,可见性,有序性

3.2原子性

3.2.1概念:

原子性:

是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中 断操作,要不执行完成,要不就不执行。

原子操作:

指不可中断的一个或一系列操作

如:id=0是原子操作,id++不是原子操作,id=0,tmp=id+1,id=tmp

3.2.2线程安全:

原子操作(基本数据类型的访问和读写),

原子类(concurrent包下AtomicInteger、AtomicLong、AtomicReference)

3.2.3线程不安全:

非原子操作,非原子操作可转化为原子操作(通过synchronized,lock)

3.3可见性

3.3.1概念:

1. 可见性就是指

当一个线程修改了线程共享变量的值,

其它线程能够立即得知这个修改, 即变量的新值

2. 可见性,

是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。

也就是一个线程修改的结果。另一个线程马上就能看到

3.3.2导致共享变量在线程间不可见的原因:

1.线程的交叉执行——非原子性

2.重排序结合线程交叉执行——非原子性

3.共享变量更新后的值没有在工作内存和主内存间及时更新——非可见性

3.3.3可见性实现过程:

Java内存模型是通过

在变量修改后将新值同步回主内存,

在变量读取前从主内存刷新变量值 

这种依赖主内存作为传递媒介的方法来实现可见性的,

无论是普通变量还是volatile变量都是如此

3.3.4可见性的实现方法:

Volatile

规则:

工作内存中修改的新值能立即同步到主内存,

以及每使用前立即从内存刷新。

volatile只能让 [ 被它修饰的变量 ] 具有可见性,但不能保证它具有原子性。

比如 :  volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题

底层原理:

1.通过加入内存屏障和禁止重排序优化来实现

2.对volatile变量执行写操作时,会在写操作后加入一条store屏障指令,即强迫线程将最新的值刷新到主内存中;

3.而在读操作时,会加入一条load屏障指令,即强迫从主内存中读入变量的值

volatile本质:是在告诉JVM当前变量在寄存器中的值是不确定的,

使用前,需要先从主存中读取,因此可以实现可见性。

线程写volatile变量的过程:

1. 改变线程工作内存中volatile变量副本的值

2. 将改变后的副本的值从工作内存中刷新到主内存中

线程读volatile变量的过程:

1. 从主内存中读取volatile变量的最新值到线程的工作内存中

2. 从工作内存中读取volatile变量的副本

Synchronized

JMM对synchronized的两条规定:

线程解锁前,必须把共享变量的最新值刷新到主内存中

线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁是同一把锁)

线程执行互斥代码的过程:

1.获得互斥锁

2.先清空工作内存

3.在主内存中拷贝最新变量的副本到工作内存

4.执行代码

5.将更改后的共享变量的值刷新到主内存中

6.释放互斥锁。

 Final

被final修饰的字段是构造器一旦初始化完成,并且构造器没有把“this”引用传递出去,那么在其它线程中就能看见final字段的值。

 Synchronized和Volatile的比较

1)Synchronized保证内存可见性和操作的原子性
    2)Volatile只能保证内存可见性
    3)Volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。)
    4)volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化).
    5)volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。

3.4有序性

3.4.1概念:

如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。

前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存中主内存同步延迟”现象。

 

3.4.2保证线程之间操作的有序性

a. volatile

volatile关键字包含了禁止指令重排序的语义

b. synchronized

由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则来获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入

 

3.4.3先行发生原则:

指Java内存模型中定义的两项操作之间的依序关系,如果说操作A先行发生于操作B,其实就是说发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包含了修改了内存中共享变量的值、发送了消息、调用了方法等

 

3.4.4先行原则的作用:

判断数据是否存在竞争,线程是否安全

3.4.5注意

一个操作”时间上的先发生“不代表这个操作会是”先行发生“,那如果一个操作”先行发生“是否就能推导出这个操作必定是”时间上的先发生“呢?也是不成立的,一个典型的例子就是指令重排序。所以时间上的先后顺序与先生发生原则之间基本没有什么关系,所以衡量并发安全问题一切必须以先行发生原则为准。

3.5总结

关键字\分类     原子性 可见性 有序性
volatile  
Synchronized
final    

 

应用场景

1. spring多线程访问共享数据处理

使用ThreadLocal方式,为每一个线程维护自己的一份变量副本。

案例分析

1) 案例我们结合懒汉式去讲线程安全(但对于我们企业级开发肯定不适用这种方式实现单例,具体请查看【单例详解】这篇文章)

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * @Description:懒汉式   
 * @author:侠客人生    
 * @date:2017-4-18 上午7:18:56   
 * @version:V1.0  
 * @Copyright:2017 侠客人生 Inc. All rights reserved.
 */
public class Singleton implements Serializable {
   //3 创建变量来存储实例
   /**
    * 通过volatile 让instance具有可见性,但不能保证它具有原子性。
    */
   private static volatile Singleton instance = null;

   // 定义变量记录调用的次数
   /**
    * AtomicInteger 原子操作,线程安全
    */
   private static AtomicInteger count = new AtomicInteger(0);
   
   //私有构造方法,好在内部控制创建实例的数目
   private Singleton() {
      count.incrementAndGet();
   }
   
   public void show(){
      System.out.println("初始化实例次数:"+count);
   }

   /**
    * 通过这个单例问题大家一定要学会两个编程思想
    *  1) 延迟加载的思想
    *  2) 缓存思想
    *  我们再开发过程中,这两个思想会在我们的项目中经常使用,我们可以借鉴懒汉式去写自己的缓存来提高性能
    */
   //2.提供一个全局访问点
   //避免先生鸡还是先有蛋的问题,我们static 让其变成类级方法
   public static Singleton getInstance(){
      //4 判断我们instance 是否为空 B
      if(instance == null){
         /**
          * synchronized 保证其操作的原子性
          */
         synchronized (Singleton.class) {
            if(instance==null){
               //4.1 直到需要用我才去创建 A B
               instance = new Singleton();
            }
         }
        
      }
      //4.1 直接返回已经创建好的实例
      return instance;
   }

   /**
    * 从时间和空间角度分析:时间 换 空间
    * 从线程安全角度分析:线程不安全
    */
   
     private Object readResolve() {
           return instance;
    }
}

© 著作权归作者所有

共有 人打赏支持
侠客人生
粉丝 14
博文 43
码字总数 82954
作品 0
朝阳
InheritableThreadLocal详解

1、简介 在上一篇 ThreadLocal详解 中,我们详细介绍了ThreadLocal原理及设计,从源码层面上分析了ThreadLocal。但由于ThreadLocal设计之初就是为了绑定当前线程,如果希望当前线程的ThreadL...

沈渊 ⋅ 04/12 ⋅ 0

Java编程基础知识点和技术点归纳

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/23 ⋅ 0

java面试必备之ThreadLocal

按照传统的经验,如果某个对象是非线程安全的,在多线程环境下对象的访问需要采用synchronized进行同步。但是模板类并未采用线程同步机制,因为线程同步会降低系统的并发性能,此外代码同步解...

编程老司机 ⋅ 05/16 ⋅ 0

Java面试需要准备哪些多线程并发的技术要点

一、概念 什么是线程 一个线程要执行任务,必须得有线程 一个进程(程序)的所有任务都在线程中执行的 一个线程执行任务是串行的,也就是说一个线程,同一时间内,只能执行一个任务 多线程原理 同一...

码蚁说架构 ⋅ 05/31 ⋅ 0

Java多线程学习(五)线程间通信知识点补充

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀 ⋅ 04/16 ⋅ 0

Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀 ⋅ 04/16 ⋅ 0

Java 编程之美:并发编程高级篇之一

本文来自作者 追梦 在 GitChat 上分享 「Java 编程之美:并发编程高级篇之一」 编辑 | 工藤 前言 借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了。 ...

gitchat ⋅ 05/24 ⋅ 0

ZK7.0.3中从MongoDB下载文件

问题 在完成Spring从MongoDB中下载文件之GridFS之后,现在需要在ZK7.0.3的ViewModel中下载该文件。 思路 先从MongoDB获取到,以及把转化为ZK的Filedownload能够使用的进行文件下载。 实现 导...

亚林瓜子 ⋅ 06/12 ⋅ 0

Java多线程学习(四)等待/通知(wait/notify)机制

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀 ⋅ 04/16 ⋅ 0

Java开发学习之三版本简介 java编程

  Java编程语言,在更迭迅速的互联网领域多年屹立不倒,足以得见Java这门语言旺盛的生命力,因此,会有很多想要进入互联网领域的朋友,想要学Java来转行开发。但是,所谓“隔行如隔山”,j...

老男孩Linux培训 ⋅ 06/05 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

磁盘管理—逻辑卷lvm

4.10-4.12 lvm 操作流程: 磁盘分区-->创建物理卷-->划分为卷组-->划分成逻辑卷-->格式化、挂载-->扩容。 磁盘分区 注: 创建分区时需要更改其文件类型为lvm(代码8e) 分区 3 已设置为 Linu...

弓正 ⋅ 21分钟前 ⋅ 0

Spring源码解析(六)——实例创建(上)

前言 经过前期所有的准备工作,Spring已经获取到需要创建实例的 beanName 和对应创建所需要信息 BeanDefinition,接下来就是实例创建的过程,由于该过程涉及到大量源码,所以将分为多个章节进...

MarvelCode ⋅ 41分钟前 ⋅ 0

a href="#"

<a href="#">是链接到本页,因为你有的时候需要有个链接的样式,但是又不希望他跳转,这样写,你可以把这个页面去试试

颖伙虫 ⋅ 48分钟前 ⋅ 0

js模拟栈和队列

栈和队列 栈:LIFO(先进后出)一种数据结构 队列:LILO(先进先出)一种数据结构 使用的js方法 1.push();可以接收任意数量的参数,把它们逐个推进队尾(数组末尾),并返回修改后的数组长度。 2....

LIAOJIN1 ⋅ 48分钟前 ⋅ 0

180619-Yaml文件语法及读写小结

Yaml文件小结 Yaml文件有自己独立的语法,常用作配置文件使用,相比较于xml和json而言,减少很多不必要的标签或者括号,阅读也更加清晰简单;本篇主要介绍下YAML文件的基本语法,以及如何在J...

小灰灰Blog ⋅ 56分钟前 ⋅ 0

IEC60870-5-104规约传送原因

1:周期循环2:背景扫描3:自发4:初始化5:请求6:激活7:激活确认8:停止激活9:停止激活确认10:激活结束11:远程命令引起的返送信息12:当地命令引起的返送信息13:文件传送20:响应总召...

始终初心 ⋅ 今天 ⋅ 0

【图文经典版】冒泡排序

1、可视化排序过程 对{ 6, 5, 3, 1, 8, 7, 2, 4 }进行冒泡排序的可视化动态过程如下 2、代码实现    public void contextLoads() {// 冒泡排序int[] a = { 6, 5, 3, 1, 8, 7, 2, ...

pocher ⋅ 今天 ⋅ 0

ORA-12537 TNS-12560 TNS-00530 ora-609解决

oracle 11g不能连接,卡住,ORA-12537 TNS-12560 TNS-00530 TNS-12502 tns-12505 ora-609 Windows Error: 54: Unknown error 解决方案。 今天折腾了一下午,为了查这个问题。。找了N多方案,...

lanybass ⋅ 今天 ⋅ 0

IDEA反向映射Mybatis

1.首先在pom文件的plugins中添加maven对mybatis-generator插件的支持 ` <!-- mybatis逆向工程 --><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-ma......

lichengyou20 ⋅ 今天 ⋅ 0

4.10/4.11/4.12 lvm讲解 4.13 磁盘故障小案例

准备磁盘分区 fdisk /dev/sdb n 创建三个新分区,分别1G t 改变分区类型为8e 准备物理卷 pvcreate /dev/sdb1 pvcreate /dev/sdb2 pvcreate /dev/sdb3 pvdisplay/pvs 列出当前的物理卷 pvremo...

Linux_老吴 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部