java 内存模型 ThreadLocal 与 volatile

一. 内存模型

多线程的三大特性

  • 原子性, 一个线程操作或多个操作共享数据时,要么全部执行成功,要么都不执行
  • 可见性,当多个线程访问同一个变量时,如果一个线程修改了变量的值,其他线程能够立即查看获取到修改后的值, 若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
  • 有序性,线程执行的顺序按照代码的先后顺序执行,jvm底层为了提高程序运行效率,可能会对输入代码进行优化,进行重排序,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的, 重排序对单线程运行不会有影响,而多线程就不一定了

在内存模型中,内存分为本地内存与主内存,本地底层是线程私有的,主内存中是共享的数据本地,当多线程执行时,首先在主内存中将需要的数据读取到当前线程的本地内存中,当线程在修改数据时首先修改的是本地内存中的数据,修改完毕后,在将本地内存中的数据刷出给共享内存,假设在线程A在本地内存中修改了数据,在还没有将修改的值刷出给主内存的时候,线程B执行读取了主内存中的这个数据,就会造成数据不一致的问题
在这里插入图片描述

二. volatile

  1. 保证此变量对所有的线程的可见性,volatile 保证了新值能立即同步到主内存,以及其他线程每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。
  2. 禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
  3. volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

三. ThreadLoacl 线程局部变量

  1. 使用场景: 假设现在有一个变量,该变量是属于线程的,就是当前线程改变该变量的值,不会对其它线程造成影响,其它线程读取该变量时还是原来的值, 使用ThreadLocal,注意Threadlocal有可能发生内存泄漏的问题
  2. 底层原理: 可以理解为在执行时,每个线程都会以当前线程未key,获取这个变量放入一个map集合容器中,当获取,修改时,通过当前线程的key去获取自己的那份
  3. 使用示例
  • 存有需要设置线程私有变量的类Res ,该类中有一个计数变量,每次执行累计加1线程私有threadLocal
class Res {
    private Integer count;
    
    /*使用Threadlocal创建一个属于每个线程的变量
    * 只要执行这段代码的线程都会拥有这个变量,变量名就可以看做是Threadlocal对象名
    * 并且每个线程修改这个变量时,修改的是各自的副本,不影响其他线程的使用
    * 创建一个Thradelocal的对象,根据变量的类型,设置Threallocal的泛型类型
    * (变量可以是任何类型,一般为基本类型,或包装类型)对象后设置返回这个变量初始化值的方法
    * (也可以看为是声明一个属于每个线程的变量,初始化为0 */
   public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
      //返回该线程局部变量初始化值
      protected Integer initialValue() {
         return 0;
      };
   };
   
   //调用该方法,设置线程的局部变量累计+1,然后返回(先获取局部变量+1后再设置到局部变量里)
   public Integer getNum(){
        //获当前线程中局部变量值 然后+1
       Integer count= threadLocal.get()+1;
       //把+1后的count在设置给属于每个线程的变量
        threadLocal.set(count);
        //再次获取修改后的属于每个线程的变量
       return  threadLocal.get();
    }

	public void remove(ThreadLocal<Integer> threadLocal){
		//删除线程中的私有变量
		threadLocal.remove();
	}
  • 调用测试
public class ThreadLocaDemo2 extends Thread {
    private Res res;
    //初始化线程对象需要Res对象,因为在线程对象中
    //的run方法中要调用Res的方法,进而对Res中的某个
    //方法实现多线程
    public ThreadLocaDemo2(Res res) {
        this.res = res;
    }
    @Override//run方法中调用Res中的getNum方法
    public void run() {
        //循环调用getNum方法(getNum方法每执行一次,对执行这个方法的线程
        // 的局部变量+1,然后返回调用这个方法的线程的局部变量)
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + res.getNum());
        }
    }
    public static void main(String[] args) {
        //创建Res对象,将res对象传入ThreadLocaDemo2中,创建自定义线程类对象
        Res res = new Res();
        ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);//线程1
        ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);//线程2
        ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);//线程3
        //运行线程1.会自动运行run方法,循环调用res中的getNum方法,
        // 获取到线程1的局部变量
        threadLocaDemo1.start();
        //运行线程2.会自动运行run方法,循环调用res中的getNum方法,
        // 获取到线程2的局部变量
        threadLocaDemo2.start();
      /*查看运行结果,会发现,虽然线程1在运行后循环调用了getNum
      * 最终把线程1的变量修改为了3,
      * 当线程2在去运行,循环调用getNum时,局部变量还是原来的0开始
      * 线程1的修改不会影响到线程2*/
    }
}
发布了48 篇原创文章 · 获赞 0 · 访问量 574

猜你喜欢

转载自blog.csdn.net/qq_29799655/article/details/105522349
今日推荐