java内存模型与线程

在前面已经看了java的内存模型,主要介绍的还是java运行时的内存结构,对于内存模型没怎么涉猎,正好在这里正好详细介绍java的内存模型,并且介绍一下线程,

1、主内存和工作内存

(1)模型

在这里插入图片描述
上面这个更详细一些,说明一下,这里的主内存你可以当成进程占领的内存单元,而Save和Load操作只是操作,也相当于主内存和工作内存之间的流程

java内存模型规定所有的变量都要存储主内存中,每条线程都有一个自己的工作内存(保存了被该线程使用到的变量主内存副本拷贝),线程对变量的操作都必须在工作内存中进行,而不能直接读写在工作内存,但是可以靠SaveLoad等操作来更新工作内存和主内存中变化的变量,

(2)内存间交互操作

这里其实就是Save和Load的用途

  1. 锁定(lock):作用于主内存的变量,标识为被一条线程占用
  2. 解锁(unlock):解锁上面的锁定状态,其线程就可以占用了
  3. 读取(read):作用于主内存的变量,把一个变量读取出来,为后面载入做数据的准备
  4. 载入(load):把读取的数据载入到工作内存中
  5. 使用(use):作用于工作内存的变量,它把工作内存的一个变量传递给执行引擎,每当 遇到需要使用此变量的值的字节码指令时就会执行此操作
  6. 赋值(assign):作用于工作内存变量,把上面使用(use)的变量的值需要赋值时就会执行这个操作
  7. 存储(store):把工作内存中一个变量的值传输到主内存
  8. 写入(write):把存储过来的值放入主内存中(如果是改变值就改变值)

对于上面的8中操作需要满足下面的规则

  1. 读取和载入必须一起,存储和写入必须一起,锁定和解锁必须一起,看上面的你应该也能理解为什么
  2. 不允许丢弃赋值操作,这里丢弃指的是不往主存中同步这个变量的新值
  3. 不允许一个线程无原因的进行工作内存和主存中的变量进行同步,减少无意义的频繁更新
  4. 一个新变量只能在主存中"诞生",不允许在工作内存中直接使用一个未被初始化的变量,这个地方我是这么理解的,新变量必须从主内存中读取,载入过来,如果创建一个新变量就必须先往主内存中放,再从主内存读取到工作内存
  5. 一个变量只能被一个线程锁定,但是可以多次锁,但是必须解锁相同的次数才能真正的解锁这个变量
  6. 在对一个变量解锁前,必须把此变量同步回主内存,这里也明白了,其他的操作大部分都是在锁定和解锁期间完成的
(3)volatile关键字

为什么在这里说这个呢?
因为这个关键字为什么这么强大可以从这里分析出来

两个特点:

  1. 可见性:当一个线程修改了被此关键字修饰的变量,那其他的线程是可以立即知道的(即使可以存在工作线程不一致的情况,但是执行引擎每次使用都刷新,看不到不一致的情况,所以就可以认为是一致的)而普通变量就不行,因为需要主内存同步才行,还是有延迟
  2. 禁止指令重排序:对于程序而言只要最后的结果是正确的,对于里面的执行顺序可以不按逻辑来,比如int i=5int j=6的前面,但是在运行的时候有可能j先执行,i后执行,但是加上volatile 就没问题了,在此关键字前面的必须全部执行完才能执行此关键字的程序,后面的也不允许提前于此程序,原因就是在汇编语言里,加了lock 相当于加锁,当一个内存屏障

对于可见性你肯定还是有迷惑的,立即知道是怎么立即知道的呢?
查询IA32手册,对于lock的前缀 它的作用使得本CPU的Cache写入内存,该写入动作也会引起别的CPU或者别的内核无效化其Cache
通俗点讲就是你这个被volatile修饰的变量当被修改后肯定要存储,写入内存的,但是在你修改时别的线程如果有此变量,那别的线程的此变量就会无效化,需要执行引擎重新去主存中读取载入新的此变量。

有一个误区volatile能保证原子性?其实是不能的,因为如果要让它保证原子性,那就需要它修饰的变量进行的操作是原子性的,区别就是i++i=1的区别,一个不是原子性操作,一个是原子性操作。

volatitle同步机制的性能是优于锁(synchronized和java.util.concurent包下的锁),CAS和AQS也是运用了volatitle 的性能,
如果volatitle能满足应用场景的需求就可以用它,不行的话再考虑锁。

(4)先行发生原则

这个规则是非常重要的,因为前面我们说了指令重排序

如果我的程序中没有volatitle的变量,那我的程序会不会乱逃呢?比如赋值比定义快?这个是不可能的,要不我们的程序还怎么运行,它都找不到定义的此变量肯定就不能赋值了,

下面有一些规则,我就不细写了,看看就懂

  1. 程序次序规则:一个线程内,按照控制流顺序执行
  2. 管程锁定规则:解锁先发生于后面同一个锁的加锁
  3. volatitle变量规则:文章上面
  4. 线程启动规则:线程的start()最先发生
  5. 线程终止规则:线程终止在最后发生
  6. 线程中断规则:线程interrupt()方法的调用先行发生于被中断线程的代码的检测和中断事件的发生
  7. 对象终结规则:一个对象的发生初始化在此对象执行finalize()之前
  8. 传递性:A比B先发生,B比C先发生,那么A肯定比C先发生

2、java与线程

(1)线程的实现

实现线程有三种方式

  1. 内核线程实现
  2. 用户线程实现
  3. 用户线程加轻量级线程实现

内核线程实现:直接由操作系统内核支持的线程,这种线程由内核来完成调度,但程序一般不会直接去使用内核,而是使用内核线程的一种高级接口-轻量级进程(这做一下区分,在用户态中,如果直接和内核线程对应的线程叫轻量级进程不对应就叫用户线程),也就是我们通常所说的线程,为了区分,内核里的线程叫内核线程,轻量级线程内核线程的比是1:1,因为有内核线程的支持,即使轻量级进程阻塞了也不会影响整合进程的工作。局限行:系统调用的代价较高,而且需要在内核态和用户态之间来回切换,耗资源,而且内核线程是有限的。

用户线程实现: 只在用户态执行线程,对于线程的分类,只要不是内核线程,其他的一律为用户线程,因为不能用内核线程,效率会受到限制,而且还很麻烦,线程模型是进程线程的比例:1:N,

和上面内核线层的区别是这里一个进程可以有很多线程,而上面的线程数量会受内核线程的限制,所以为什么上面的是和内核线程是比例,而这个是和进程的比例

用户线程加轻量级进程之间的混合实现

这里的比例是N:M,就是多对多,因为混杂着轻量级进程用户线程这两种,这样既可以使用内核提供的线程调度功能及处理器映射,并且用户线程系统调用需要用到轻量级进程来完成,打打降低了完全阻塞的风险,还不受内核线程数量的限制。

对于Sun JDK而言,java在Windows和Linux都是使用1:1的线程模型来实现的,一条java线程映射一条轻量级进程

(2)java线程的调度和状态

线程的调度是指系统为线程分配处理器使用权的过程

分为两种:

  1. 协同式线程调度:执行时间由线程决定,主动通知系统更换,实现简单,但是执行时间不可控,阻塞就完了
  2. 抢占式线程调度:由系统来分配执行时间,时间可控,不会导致进程阻塞,java就是用这种方式

线程的状态有6种(但是在java语言中定义了5种)

  1. 新建:线程建立
  2. 运行:线程运行
  3. 无限期等待:等待被其他线程唤醒wait
  4. 限期等待:无须被唤醒,到时间会醒的,sleep
  5. 阻塞:和等待的区别是在等待获取一个排它锁
  6. 结束:终止线程状态
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值