多线程(二:JAVA内存模型)

JAVA内存模型(JMM  java memory model)

如上图即JMM,JMM是为了规范内存数据和工作空间的数据交互。   主内存是共享的数据区域,工作内存是私有的数据区域,基本数据类型直接分配到工作内存,引用的地址存放在工作内存,引用的对象存放在堆里。

工作方式:1 线程直接在工作空间里修改私有数据。   2 线程修改共享数据。首先把数据复制到工作空间中,在工作空间中修改,修改完成后刷新到主内存中。

三个特性 :原子性,不可分割。    可见性:线程之间只能操作自己工作空间的数据。    有序性:程序的顺序不一定就是程序的执行顺序。编译期间会了提高性能发生编译重排序和指令重排序。

 

   导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性,有序性的办法就是禁止缓存和编译优化。虽然解决了问题,但是性能很差,合理的方案是按需禁用缓存和编译优化。JMM规范了JVM如何按需禁用缓存和编译优化的方法,这些方法有volatile,synchronized和final三个关键字,以及六项happens-before原则。这里提一下as-if-serial,他是单线程环境下重排序后不影响程序的执行结果。

     volatile: volatile修饰一个变量,他告诉编译器,对这个变量的读写不能使用CPU的缓存,而要从内存中读取或者写入。java 1.5后对volatile进行了加强,happens-before原则。

happens-before:

Happens-Before 的意思是前面一个操作的结果对后续的操作可见。Happens-Before 约束了编译器的优化行为,允许编译器优化,但是要求编译器遵循Happens-Before 原则。

1 程序的顺序性规则   

        程序前面对某个变量的修改一定是对后续操作可见的。

2 volatile变量规则 

        volatile变量的写操作,Happens-Before 与volatile变量的读操作,其实就是禁用缓存。

3 传递性   

         A  Happens-Before  B  ,B Happens-Before  C  则A Happens-Before C。

4  管程中锁的规则   

        对一个锁的解锁  Happens-Before  于后续对这个锁的加锁。管程是一种通用的同步语句,在JAVA里就是synchronized。

synchronized (this) { // 此处自动加锁
  // x 是共享变量, 初始值 =10
  if (this.x < 12) {
    this.x = 12; 
  }  
} // 此处自动解锁

如上X初始值为10,线程A执行完代码块后x变成12,后续线程B进来后看到线程A把X改成了12。

5  线程start规则

         在线程A中启动线程B,子线程B能够看到主线程A在启动子线程B前的操作。

6     线程join原则

        线程A中调用B的join方法并成功返回,那么B中的任意操作Happens-Before 于 该join方法的返回。

7     线程中断规则

        对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

8     对象终结规则:

        一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

final:

       final修饰的变量,告诉编译器这个变脸是不会变的。

 

互斥锁:解决原子性问题

      原子性问题源头是线程切换。操作系统做线程切换是依赖CPU中断的,禁止CPU发生中断就可以禁用线程切换。

     单核CPU的场景下,同一时刻只有一个线程执行,禁止CPU中断,意味着操作系统不会重新调度线程,禁止线程切换获得CPU使用权的线程就可以一直执行,具有原子性。但是多核场景下,两个线程同时执行,一个线程执行在CPU1上,一个执行在CPU2上,此时禁止CPU中断,只能保证CPU上的线程连续执行,但不能保证同一时刻只有一条线程执行。

     同一时刻只有一条线程执行被称为互斥。如果能保证对共享变量的修改是互斥的,那就可以保证原子性了。

synchronized锁可以实现互斥。synchronized可以修饰方法,修饰代码块,修饰类。java编译器会在synchronized修饰的方法和代码块前后自动加上加锁和解锁。当修饰静态方法时,锁定的是当前类的class对象,当修饰非静态方法时锁定的就是当前实例对象this。

管程中锁的原则:对一个锁的解锁Happens-Before 后续这个锁的加锁。前一个线程在临界区修改的共享变量,对后续(该操作在加锁之后)进入临界区的线程可见。

class SafeCalc {
  static long value = 0L;
  synchronized long get() {
    return value;
  }
  synchronized static void addOne() {
    value += 1;
  }
}

如上代码中其实是两个锁在保护一个资源。synchronized修饰的第一个是this,第二个是SafeCalc.class。因此这两个临界区资源是没有互斥性和可见性,这就会导致并发问题。

如何一把锁保护多个资源

  一把锁来管理多个问题的情况下性能较差,举个例子,取款,查看余额,修改密码等一系列用同一把锁管理,所有的操作都是串行执行的,用不同的锁对受保护的临界区资源进行精细化管理,能够提升性能,这种锁被称为细粒度锁。细粒度锁可以提高程度的并行度。

class Account {
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    synchronized(Account.class) {
      if (this.balance > amt) {
        this.balance -= amt;
        target.balance += amt;
      }
    }
  } 
}

如上例子,用Account.class作为共享的锁,Account.class是所有account对象共享的,而且这个对象是JAVA虚拟机在加载Account类的时候创建的,所以我们不用担心他的唯一性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值