java并发编程一一多线程线程安全(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34846877/article/details/81983203

1.多线程的三大特性

1.1什么是原子性

即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分,

1.2什么是可见性

当多个线程访问一个变量时,一个线程修改了这个变量的值,其它线程能够立即看到得到这个修改的值。
若两个线程在不同的cpu,那么线程1 改表 i 的值还没有刷新到主存,线程2又使用 i 的值,
那么这个i 的值肯定还是之前的,线程1 变量的修改没有看到就是可见性问题。

1.3什么是有序性

程序的执行顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序的运行效率,可能就会对输入的代码进行优化,他不保证程序中各个语句
的执行先后顺序同代码中的顺序一致,但是他会保证程序最终执行结果和代码顺序执行的
结果一致的。
示例如下:
int a = 10; //语句1
int r = 2; // 语句2
a = a + 3; // 语句3
r = a* a; // 语句4
则因为重排序,它还可能执行的顺序是:2-1-3-4,1-3-2-4
但是绝对不可能是 2-1-4-3 ,因为这个打破了依赖关系。
显然重排序对单线程运行是不会有任何问题的,但是对于多线程就不一定了,
所以我们在多线程编程时就得考虑这个问题。

2.Java内存模型

2.1什么是java内存模型

共享内存模型指的就是 java 内存模型(简称 JMM)jmm决定了一个线程对共享变量的写入时,
能对另一个线程可见。从抽象的角度来看,jmm 定义了线程和主存之间的抽象关系:线程之间的
共享变量储存在主存(main memory)中,每个线程都有一个私有的本地内存(local memory),
本地内存中储存了该线程已读/写共享变量的副本。本地内存是jmm的一个抽象概念,并不真实存在。
它涵盖了缓存,写缓存区,寄存器以及其他的硬件和编译器优化。
这里写图片描述
上图分析:
线程A 与线程B 之间如要通信的话,必须要经历下面2个步骤:
1. 首先,线程A把本地内存A 中更新过的共享变量刷新到主内存中。
2. 然后,线程B到主内存中去读线程A之前已更新过的共享变量
下面通过示意图来说明这两个步骤:
这里写图片描述
如上图所以:本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。
线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存中A 中。当线程A 和线程B
需要通信时,线程A 首先会把自己本地内存中修改的x刷到主内存中,此时主内存中x值变了1.随后,
线程B 到主内存中去读线程A更新后的X值,此时线程B的本地内存的x值也变了1.
从整体来看,这两个步骤实质是线程A 在向线程B 发消息,而且这个通信过程必须要经过主内存。
JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
总结:
什么是java内存模型?
java 内存模型 简称 jmm ,定义一个线程对另一个线程可见。共享变量存放在主内存中,每个线程
都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主存中,
所有就会发生线程安全问题。

3.Volatile

3.1什么是Volatile

可见性也就是一旦某个线程修改了该被volatile修饰的变量,他也保证修改的值会立即被更新到主内存,
当有其它线程需要读取时,可以立即获取修改之后的值
在java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存
上进行的,之后才会同步到主内存中,而加了volatile修饰符的变量则是直接读写到竹村中。
Volatile保证了线程间共享变量的及时可见性,但不能保证原子性。
代码示例:

class ThreadVolatileDemo extends Thread {
    public    boolean flag = true;
    @Override
    public void run() {
        System.out.println("开始执行子线程....");
        while (flag) {
        }
        System.out.println("线程停止");
    }
    public void setRuning(boolean flag) {
        this.flag = flag;
    }
}

public class ThreadVolatile {
    public static void main(String[] args) throws InterruptedException {
        ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
        threadVolatileDemo.start();
        Thread.sleep(3000);
        threadVolatileDemo.setRuning(false);
        System.out.println("flag 已经设置成false");
        Thread.sleep(1000);
        System.out.println(threadVolatileDemo.flag);
    }
}

已经将结构设置为false为什么还一直运行呢?
原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存的结构。
解决方法是使用volatile关键字将解决线程之间可以见性,强制线程每次读取该值的时候
都去“主内存”中取值。

3.2Volatile的特性

  1. 保证此变量对所有的线程的可见性,这里的“可见性”。
    如文本开头所述,当一个线程修改了这个变量的值,volatile保证了新值能立即同步到主存中,
    以及每次使用前立即从主存中刷新。但是普通的变量做不到这点,普通变量的值在线程间传递均需要通过主存来完成。

  2. 禁止指令重排序优化。
    有volatile修饰的变量,赋值后多执行一个“load addl $0x0,(%esp)” 操作,这个操作相当于一个
    内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU
    访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许建多条指令不安程序的
    的顺序分开发送给相应电路单元处理。)

  3. volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为他需要在本地代码中
    插入许多内存屏障指令来保证处理器不发生乱序执行。

3.3Volatile与synchronized的区别

  1. 从而我们可以看出volatile 虽然具有可见性但是并不能保证原子性。
  2. 性能方面,synchronized关键字时防止多个线程同时之sing一段代码,就会影响程序执行效率,
    而volatile 关键字在某些情况下性能要优于synchronized。
    但是要注意的volatile 关键字时无法替代synchrond 关键字的,因为volatile关键字无法保证原子性。

猜你喜欢

转载自blog.csdn.net/qq_34846877/article/details/81983203