美文网首页java基础
线程的三特性

线程的三特性

作者: isLJli | 来源:发表于2021-05-23 12:21 被阅读0次

内存模型

由前一遍文章https://www.jianshu.com/p/623cf38cc4c7讲解了内存模型,但也带来线程的三个问题:原子性、可见性、有序性

内存模型
因为CPU的运行速度特别快,而主存的运行的速度跟不上CPU的速度,造成CPU在读取主存的数据要等待很久时间,所以CPU增加了的高速缓存区把需要数据存起来,CPU需要数据的时候就从高速缓存区中获取数据的副本,虽然高速缓存区运行速度很快,但也很昂贵。高速缓存区的出现提高了CPU的执行效率,但在多线程中也随之出现数据的原子性、可见性问题

我们模仿两核CPU的执行多线程对同一数据的读取:

CPU与主存的数据读取
CPU1和CPU2分别从主存中获取a的副本,两个高速缓存区a=0,线程A通过CPU1的运算后把a赋值为3,然后把a=3的结果缓存到高速缓存区中,但是并没来的急把结果返回到主存时,线程B通过CPU2也运算操作a++,从CPU2的高速缓存区中拿到的a=0的值+1,而不是3+1,所以内存模型在多线程中会造成数据的不同步。也带出线程的三大特性:原子性、可见性、可序性

线程原子性:在多线程的情况下,数据可能同时被多个线程上同时执行,这样就造成运算结果的不一致性,线程的原子性就是数据在被一个线程执行的时候,其他线程不可以同时再运行此数据。java中可以使用synchronized可以解决线程的原子性问题。

线程可见性:原子性解决了多线程同时访问数据的问题,但是CPU的高速缓存区会并不能执行后数据立刻的更新到主存中,这样会导致其他的线程对另一个线程的计算结果不可见。在java中volatile可以接口线程的可见性问题。

线程可序性:这个也是指令重排序问题,就是CPU为了内部的处理器单元的充分利用,会在对单线程结果正确的情况下,对代码指令进行非顺序执行。

原子性——Synchronized

Synchronized:每一个对象都有一个锁,在执行Synchronized就会为线程获取其锁,其他线程再访问Synchronized的内容时,如果没有对象的锁就不能访问。所以保证了多线程的原子性。

通过一个Bean数据类学习Synchronized的使用:

public class StudentBean {
  // 每个对象都有一个锁

  private String name;
  private String age;
  private String sex;

  // 1.在方法中加锁
  public synchronized void setNameAndAge(String name, String age) {
      this.name = name;
      this.age = age;
  }

  // 2.在代码块中加锁
  public void setName(String name) {
      synchronized (this) {
          this.name = name;
      }
  }

  // 3.在静态方法中加锁,这里锁的是"类的对象,即Class的对象"
  public static synchronized void start() {
      System.out.println("start");
  }

  // 执行完synchronized方法或代码块后,就会释放对象锁
  
  // 没有获取对象锁的线程,可以访问没有被synchronized的方法或代码块
  public void setAge(String age) {
      this.age = age;
  }

}

Synchronized可以修饰在方法、代码块、静态方法中,其中方法、代码块锁的是java对象,静态方法锁的是Class对象。执行完锁的方法后,拥有锁的线程就会释放对象锁。其他没有锁的线程就要么在CPU等一会,要么进入阻塞状态。

Synchronized原理
看看synchronized代码块的通过反编译后的字节码是怎样的。

public class SynchronizedTest {
  public static void main(String[] args) {
      //通过synchronized修饰代码块
      synchronized (SynchronizedTest.class) {
          System.out.println("this is in synchronized");
      }
  }
}
反编译字节码

同步代码块在monitorenter之后开始,同步代码块在monitorenter结束。
monitor:锁可以理解为对象锁,它是java虚拟机实现的,底层依赖操作系统的Mutex Lock实现,每一个java对象都有都拥有monitor锁。

monitorenter:执行monitorenter时,会先尝试获取锁,如果monitor没有被锁,或者已经拥有的monitor锁,锁的计数器就会+1,并开始执行同步代码。

monitorexit:执行monitorexit时,锁的计数器就会-1,如果计数器为0时,线程就会释放monitor锁,其他线程就可以获取monitor锁。

  1. 静态方法:
public class SynchronizedTest {


  public static void main(String[] args) {
      doSynchronizedTest();
  }
  //通过synchronized修饰方法
  public static synchronized void doSynchronizedTest(){
      System.out.println("this is in synchronized");
  }
}
反编译字节码
静态方法并没有出现monitorenter和monitorexit,而是执行了ACC_SYNCHRONIZED,ACC_SYNCHRONIZED在执行同步代码块之前会获取monitor锁,再执行完成同步方法后会释放monitor锁。
  1. JDK1.6后的锁优化
    1.6之前,如果其他线程获取不了锁,就会进入阻塞状态,阻塞状态就会涉及上下文切换,这将消耗很多的时间,所以1.6后优化了锁频繁的上下文切换问题。


    对象头的锁信息

首先线程进来获取锁时,会先获取偏向锁,偏向锁记录着线程Id,如果偏向锁已经有线程锁定,那就就会进入轻量级锁,在轻量级锁的线程就会在CPU执行回旋操作,如果已经获取的锁的线程很快的执行完成,那么就到轻量级锁执行,如果等待的时间久了,就进入重量级锁,那么线程就进入阻塞状态。所以这就优化了线程没有获取锁就直接阻塞问题。

可见性——volatile

被Volatile修饰的变量,在变量会修改后的值,对于其他的线程来说一样是及时可见的。

private var volatile hasLoadingHeader = false

它是如何实现的,被volatile修饰的变量在修改之后,会从高速缓存立即更新到主存中,并让主存发送一个通知给其他线程,告诉其他线程当前的变量已经更新,你高速缓存区的变量已经失效了,如果你要使用的话,请到主存拿去最新的变量值。

相关文章

  • 线程的三特性

    内存模型 由前一遍文章https://www.jianshu.com/p/623cf38cc4c7[https:/...

  • 单例模式的几种写法

    一、饿汉式 特性:立即创建 线程安全 没有延迟加载 二、懒汉式 特性:延迟创建,线程不安全 三、加锁的懒汉式 特性...

  • OC-属性简介

    OC中,任何属性都有3种特性,分别是 1.多线程特性2.读/写特性3.内存管理特性 多线程特性 多线程特性有non...

  • [Java]重学Java-如何保证线程安全

    保证线程安全的三大特性 由于CPU是多线程切换执行的,那么对于操作的程序,我们需要保证3个特性: 原子性 原子(a...

  • 多线程之线程安全性

    多线程环境下使用非线程安全类会导致线程安全问题。线程安全问题表现为原子性,有序性,可见性 在讲述线程安全三大特性之...

  • 多线程内部情况

    三种线程模型一对一模型一对一线程特性一对一线程优点一对一线程缺点一对一线程创建方式一对多模型一对多模型特性一对多模...

  • jdk的内置锁

    前言 Java并发编程为了保证线程安全,需要维护共享变量的三种特性-原子性、可见性和有序性。而锁可以实现这三种特性...

  • 多线程中的三大特性

    多线程编程要确保并发程序正确地执行,必须要保证原子性、可见性以及有序性,缺一不可,不然就可能导致结果执行不正确。 ...

  • # iOS基础 # 线程知识整理

    什么是线程 1、线程的定义、状态、属性 进程 线程 线程与进程的共同点和区别 线程的状态 线程的特性 2、线程之间...

  • 优雅循环打印abc的方法

    调用locksupport 可以 指定唤醒哪个线程特性优雅实现线程循环打印

网友评论

    本文标题:线程的三特性

    本文链接:https://www.haomeiwen.com/subject/pxnhjltx.html