volatile,ThreadLocal,synchronized,Lock

零、基本概念

内存管理模型JMM(Java Memory Model):Java 内存模型来屏蔽掉各种硬件和操作系统的内存差异,达到跨平台的内存访问效果。

       主内存:所有的线程所共享。

                    JMM规定了所有的变量都存储在主内存中,此处的主内存仅仅是虚拟机内存的一部分

       工作内存:每个线程自己有一个,不共享。

        线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作(读取、赋值),都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者之间的交互关系如下图:


在并发编程中一般要考虑三个基本概念:

        原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

                        (在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。)

         举个栗子:一下操作哪些是原子操作

i = 0;        --1
j = i ;       --2
i++;          --3
i = j + 1;    --4

         1—在Java中,对基本数据类型的变量和赋值操作都是原子性操作;
         2—包含了两个操作:读取i,将i值赋值给j
         3—包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;
         4—同三一样

        可见性 :一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。
                        (Java 中 volatile、synchronized 和 final 实现可见性)
        在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的

        有序性:即程序执行的顺序按照代码的先后顺序执行。

                          (Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性)

        在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序它不会影响单线程的运行结果, 

        但是对多线程会有影响。



一、volatile

 1.1 什么是volatile

     volatile,Java语言提供的一种稍弱的同步机制,volatile可以保证线程可见性提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的当一个变量定义为 volatile 之后,将具备两种特性:

            1.保证此变量对所有的线程的可见性

volatile 变量,JVM 保证了每次读变量都从主内存中读变量本身,跳过工作内存这一步。

volatile 变量,每次读变量都从工作内存中读变量的副本

            2.禁止指令重排序优化(有序性)

 1.2 volatile的性能

    volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令。

 1.3 volatile的使用条件

在某些场景下,因为不会引起线程的上下文切换,volatile相当于轻量级的synchronized,但使用volatile必须满足两个条件:

1、对变量的写操作不依赖当前值

例如:

volatile int a = 1;

多线程下执行a++,虽然可以保证可见性,但因为非原子操作,无法保证原子性。


2、该变量没有包含在具有其它变量的不变式中:下界总是小于或等于上界

例如:

public class NumberRange {
    private volatile int lower, upper;
    public int getLower() { return lower; }
    public int getUpper() { return upper; }
    public void setLower(int value) { 
        if (value > upper) 
            throw new IllegalArgumentException(...);
        lower = value;
    }
    public void setUpper(int value) { 
        if (value < lower) 
            throw new IllegalArgumentException(...);
        upper = value;
    }
}

假设初始状态  lower=1 , upper=9 ,同时A,B两个线程同时进行操作,A执行setLower(8),B执行setUpper(3),

结果为lower=8 , upper=3,这是不合理的


 1.4 volatile的使用场景

状态标志

volatile boolean shutdownRequested;
...
public void shutdown(){ 
   shutdownRequested = true;
}
public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}

可以在循环外部--其他线程--来终止循环,然而,使用 synchronized 块编写循环要比使用volatile 状态标志编写麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。


双重检查模式 (DCL)

public class Singleton {  
    private volatile static Singleton instance = null;  
    private Singleton(){};  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized(this) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}
1.5、volatile的总结

与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。

如果严格遵循 volatile 的使用条件即变量真正独立于其他变量和自己以前的值 ,在某些情况下可以使用 volatile 代替 synchronized 来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。

本文介绍了可以使用 volatile 代替 synchronized 的最常见的两种用例,其他的情况我们最好还是去使用synchronized

二、ThreadLocal

2.1、学习ThreadLocal一定要注意一下两点:

       1、 ThreadLocal不是用来解决多线程的共享对象访问问题

       2、 它也不是“本地线程”,ThreadLocal并不是一个Thread,而是Thread的局部变量

            划重点:当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

2.2、ThreadLocal要解决的问题和使用场景

    1、要解决的问题:保证缓存变量实现线程内变量共享)的线程安全

             假设有一个需求,需要缓存一个变量,能够线程内正常存取,且不受其他线程影响。

             有人可能会想到静态map,但会有两个问题,其他线程的影响和访问并发。

    2、使用场景

          2.1、日志的打印(同一线程的日志一起打印,或者说一次事务的日志一起打印,因为一般默认一次事务都是由同一个线程执行的,将事务的日志保存在线程局部变量当中,当事务执行完成的时候统一打印)

          2.2、数据路连接、事务,事务是和线程绑定起来的,Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection

          2.3、调用SimpleDataFormat的工具类建议使用ThreadLocal

          2.4、Session管理

2.3、ThreadLocal的方法


使用案例:

public class OperationLogThreadLocal {
	private static ThreadLocal<CreateOperationLogParam> userThreadLocal = new ThreadLocal<>();

	public static void setLog(CreateOperationLogParam logParam) {
		userThreadLocal.set(logParam);
	}

	public static CreateOperationLogParam getLog() {
		return userThreadLocal.get();
	}

	public static void clear() {
		userThreadLocal.remove();
	}

}

2.4、ThreadLocal的原理

ThreadLocal

ThreadLocalMap  : ThreadLocal类中的静态内部类

                                 (实质存储变量的位置,在Thread中有类型为ThreadLocal.ThreadLocalMap类型的属性threadLocals)

Entry                     : ThreadLocalMap中的静态内部类,若引用ThreadLocal(存储变量的结构)

2.4.1、存储 

            存储过程实际是往当前线程的ThreadLocalMap中存储

            key为 “this”,不是当前线程

            获取线程中的ThreadLocalMap



2.4.2、取值

                从当前线程中获取ThreadLocalMap

                从ThreadLocalMap获取值


     给ThreadLocalMap初始化值


     初始化值,可以重写

2.4.3、移除

这里,我没有贴上ThreadLocalMap中的方法源码。。。

2.5、共享问题的对比

 线程共享问题线程共享问题
实质多个线程共同操作同一份变量的安全问题单个线程操作独自的变量的安全问题
变量区别一份变量,多个线程访问(共享一份变量)一份变量,一个线程独有,线程内共享,不受其他线程影响
实现方法synchronized,Lock,volatileThreadLocal
实现思路以时间换空间以空间换时间

三、synchronized

        待续

四、Lock

        待续

五、综合对比

        待续

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值