Java并发(1)--并发基本:CPU缓存、Java内存模型、Java线程

文章目录

    • 一:CPU缓存
      • 1. CPU多级缓存
      • 2. 缓存一致性
    • 二: Java内存模型
      • 1. 主内存和工作内存
      • 2. volatile 变量
      • 3. 原子性、可见性、有序性
      • 4. 先行发生原则
    • 三: Java与线程
      • 1. 线程的实现
      • 2. Java线程调度
      • 3. Java线程状态转换

一:CPU缓存

并发:多个线程操作相同的资源,保证线程安全,合理使用资源
高并发:服务能同时处理很多请求,提高程序性能

1. CPU多级缓存

CPU多级缓存与缓存一致性

CPU的频率越来远快,相对内存快了一个数量级,对于访存的操作CPU就需要等待主存,这样会导致资源的白白浪费,因此引入了缓存机制。
cache 的工作原理是基于“局部性”原理,它包含以下两个方面:
1. 时间局部性:如果某个数据被访问,那么不久将来它很可能再次被访问。
2. 空间局部性:如果某个数据被访问,那么与它相邻的数据也可能被访问。

2. 缓存一致性

MESI协议。在MESI协议中,每个Cache的Cache控制器不仅知道自己的读写操作,而且也监听(snoop)其它Cache的读写操作。每个Cache line所处的状态根据本核和其它核的读写操作在4个状态间进行迁移。 M
乱序执行优化:处理器为提高运算速度而做出违背代码原有顺序的优化

二: Java内存模型

全面理解Java内存模型(JMM)及volatile关键字

1. 主内存和工作内存

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存完成。

主内存
主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。
工作内存
主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。

内存间交互方式为8个原子性操作和相关规定,这些规定针对 volatile  变量有一些特殊规定

2. volatile 变量

volatile内存语义
- 保证被volatile修饰的共享变量对所有线程总数可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。
- 禁止指令重排序优化。

3. 原子性、可见性、有序性

原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。由Java内存模型直接保证的原子性变量操作read、load、assign、use、store和write。在更大范围的原子性保证中,还提供了 lock 和 unlock 操作,这两个字节码指令反应到Java代码中就是同步块-synchronized 关键字。

可见性是指当一个线程修改了共享变量的值,其他线程能够立即得到这个修改。volatile、synchronized 、final 修饰的变量都可以实现可见性

有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的,这样的理解并没有毛病,毕竟对于单线程而言确实如此,但对于多线程环境,则可能出现乱序现象,因为程序编译成机器码指令后可能会出现指令重排现象,重排后的指令与原指令的顺序未必一致,要明白的是,在Java程序中,倘若在本线程内,所有操作都视为有序行为,如果是多线程环境下,一个线程中观察另外一个线程,所有操作都是无序的,前半句指的是单线程内保证串行语义执行的一致性,后半句则指指令重排现象和工作内存与主内存同步延迟现象。

4. 先行发生原则

Java内存模型存在一些天然的先行发生关系,这些先行发生关系无须任何同步器协助就已经存在。然后两个操作之间的关系不在天然先行关系内,它们就没有顺序保障,就可能进行指令重排。

三: Java与线程

1. 线程的实现

线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以独立调度
实现线程主要有三种方式

  1. 使用内核线程实现
    内核线程就是直接由操作系统内核支持的线程,由内核来完成线程间的切换。应用程序通过内核线程的一个高级借口-轻量级进程来使用线程,这种方式为一对一的线程模型。
  2. 使用用户线程
    线程完全建立在用户空间的线程上,系统内核感知不到线程存在。这种当时为一对多的线程模型。但是这种线程应对阻塞等问题时难以解决,JAVA已弃用
  3. 使用用户线程加轻量级进程混合实现
    结合两者的优点,也就是多对多线程。

对于Java线程来说,线程模型基于操作系统原生线程模型来实现,因此,不同操作平台的线程模型可能不一样。Sun JDK 为一对一的线程模型,Sloaris平台中,支持一对一和多对多。

2. Java线程调度

线程调度指系统为线程分配处理器使用权的过程,分为协同式线程调度和抢占式线程调度。
协同式线程调度:线程的执行时间由线程本身控制,线程执行完后通知系统更换到另一线程。这种方式导致线程执行时间不可控制。
抢占式线程调用:每个线程由系统分配执行时间,线程的切换不由线程本身决定,Java就是采用的这种机制。

另外,Java线程中的优先级不一定可靠。

3. Java线程状态转换

Java线程的6种状态及切换(透彻讲解)

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

你可能感兴趣的:(并发)