Java线程安全:可见性,原子性,有序性

Java线程安全

可见性,原子性,有序性

Java内存模型(JMM)

Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

  • 所有的变量都存储在主内存中。
  • 每个线程都有自己独立的工作内存,里面保持该线程使用到的变量副本。

  • 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中进行读写。
  • 不同线程之间无法直接访问其他线程工作内存的变量,所以线程间变量值的传递需要通过主内存来完成。

如何实现可见性

要实现共享变量的可见性,必须保证两点:

  • 线程修改后的共享变量值能够及时从工作内存刷到主内存中。
  • 其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中。

synchronized实现可见性

JMM关于synchronized的两条规定:

  • 线程解锁前,必须把共享变量的最新值刷新到主内存中。
  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时候需要从主内存中重新读取最新值。
线程执行互斥代码的过程:
  • 获取互斥锁
  • 清空工作内存
  • 从主内存拷贝变量最新值到工作内存中
  • 执行代码
  • 将更改后的共享变量的值刷新到主内存中
  • 释放互斥锁
Synchronized的使用
  • 修饰代码快:{}里的代码块,作用与调用对象
  • 修饰方法:整个方法,作用于调用的对象
  • 修饰静态的方法:整个静态方法,作用于所有对象
  • 修饰类:{}括起来的部分,作用于所有对象
public class SyncExample {

    //修饰代码快
    public void test1(String name){
        synchronized (this){
            for (int i = 0; i < 10; i++){
                System.out.println(name + ":" + i);
            }
        }
    }

    //修饰方法
    public synchronized void test2(String name){
        for (int i = 0; i < 10; i++){
            System.out.println(name + ":" +i);
        }
    }

    public static void main(String[] args) {
        final SyncExample sync1 = new SyncExample();
        SyncExample sync2 = new SyncExample();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> sync1.test1("sync1"));
        executorService.execute(() -> sync2.test1("sync2"));

    }
}
//结果
sync2:0
sync1:0
sync2:1
sync1:1
sync2:2
sync1:2
sync1:3
sync2:3
sync1:4
sync2:4
sync1:5
sync2:5
sync1:6
sync2:6
sync1:7
sync2:7
sync1:8
sync2:8
sync1:9
sync2:9

sync1和sync2的test1交替执行,说明同步代码块作用在当前对象,不同对象调用互不影响。

当换成test2()时,结果和上相同,说明synchronized修饰一个方法时,作用和同步代码块相同,都是作用当前对象。

所以,如果一个方法中是一个完整的同步代码快,它和synchronized修饰一个方法是等同的。

当修饰一个类时

  public static void test3(){
        synchronized (SyncExample.class){
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread() + "test3 :" + i + " ");
            }
        }
    }

Thread[pool-1-thread-1,5,main]test3 :0
Thread[pool-1-thread-1,5,main]test3 :1
Thread[pool-1-thread-1,5,main]test3 :2
Thread[pool-1-thread-1,5,main]test3 :3
Thread[pool-1-thread-1,5,main]test3 :4
Thread[pool-1-thread-2,5,main]test3 :0
Thread[pool-1-thread-2,5,main]test3 :1
Thread[pool-1-thread-2,5,main]test3 :2
Thread[pool-1-thread-2,5,main]test3 :3
Thread[pool-1-thread-2,5,main]test3 :4 

从结果上可以看出,修饰一个类时,作用于这个类的所有对象。

volatile 实现可见性

volatile通过加入内存屏障和禁止重排序优化来实现可见性

  • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
  • 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令

volatile变量在每次被线程访问时,都强迫从主内存中读取该变量的值,而当发生变化时,会强迫线程将最新的值刷新到主内存。

  • 线程写volatile变量的过程
  1. 改变线程工作内存中volatile变量副本的值。
  2. 将改变后的副本的值重工作内存刷新到主内存
  • 线程度volatile变量的过程
  1. 将主内存中读取volatile变量的最新值到工作内存中。
  2. 从工作内存中读取volatile变量的副本。

volatile不能保证原子性

private int number = 0;
number ++;

number ++ 不是原子操作,可以分为三步

1.读取number的值,2.将number值+1,3.写入最新的numnber的值

public class TestAtomicDemo {

    public static void main(String[] args) {
        AtomicDemo demo = new AtomicDemo();
        for (int i = 0; i < 10; i ++){
            new Thread(demo).start();
        }
    }

}

class AtomicDemo implements Runnable{

    private volatile int serialNumber = 0;

    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() +":"+getSerialNumber());
    }

    public int getSerialNumber(){
        return serialNumber ++;
    }
}

Thread-0:0
Thread-1:1
Thread-3:3
Thread-2:2
Thread-5:4
Thread-4:5
Thread-6:7
Thread-7:7
Thread-8:6
Thread-9:8

可以看到Thread6和Thread7的执行结果相同。

原子性

原子性:提供了互斥访问,同一时刻,只能由一个线程对它进行操作。

jdk1.5后java.util.concurrent.atomic包下面提供可常用的原子变量。CAS(Compare-And-Swap)算法保证数据原子性。

private volatile AtomicInteger serialNumber = new AtomicInteger(0);

 public int getSerialNumber(){
        return serialNumber.incrementAndGet();
    }

AtomicInteger的incrementAndGet()方法是如何保证原子性的呢?

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

    private volatile int value;
    /**
     * Atomically increments the current value,
     * with memory effects as specified by {@link VarHandle#getAndAdd}.
     *
     * <p>Equivalent to {@code addAndGet(1)}.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }

从源码(我的是JDK10)中可以看到AtomicInteger使用了一个Unsafe类的getAndAddInt方法。

/**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object {@code o}
     * at the given {@code offset}.
     *
     * @param o object/array to update the field/element in
     * @param offset field/element offset
     * @param delta the value to add
     * @return the previous value
     * @since 1.8
     */
    @HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }

getIntVolatile方法,如果没有其他线程来处理Object o,这个方法返回只v就等于offset。

weakCompareAndSetInt方法就是CAS算法核心

offset为内存值,v是预估值,v+delta为更新值,当且仅当offset==v,才会+1操作。

有序性

Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序的过程不会影响单线程的执行结果,却会影响到多线程并发执行的正确性。

volatile, synchronized,Lock,可以保证有序性

##### happens-before原则

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前的操作先于后的操作。Java虚拟机只会对没有数据依赖的代码进行重排序。
  2. 锁定规则,一个unLock操作先行发生于后面对同一个锁的lock操作。
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。
  4. 传递规则:提现了happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C
  5. 线程启动规则:假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
  6. 线程终结规则:假定线程A在执行的过程中,通过制定ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。
  7. 对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始。

原文地址:https://www.cnblogs.com/Godfery/p/10353625.html

时间: 2024-10-28 22:54:26

Java线程安全:可见性,原子性,有序性的相关文章

线程安全—可见性和有序性

什么是java的内存模型? 共享变量:一个变量可以被多个线程使用,那么这个变量就是这几个线程的共享变量. Java Memory Model (JAVA 内存模型)描述线程之间如何通过内存(memory)来进行交互,描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节. 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,但线程不能直接操作主内存中的变量,每个线程

Java线程安全 关于原子性与volatile的试验

1. 变量递增试验 1 static /*volatile*/ int shared=0;//volatile也无法保证++操作的原子性 2 static synchronized int incrShared(){//不加synchronized的话,shared最终结果值小于预期 3 return ++shared; 4 } 5 public static void testIncrShared(String[] args) throws InterruptedException { 6 s

深入理解Java虚拟机笔记---原子性、可见性、有序性

Java内存模型是围绕着并发过程中如何处理原子性.可见性.有序性这三个特征来建立的,下面是这三个特性的实现原理: 1.原子性(Atomicity) 由Java内存模型来直接保证的原子性变量操作包括read.load.use.assign.store和write六个,大致可以认为基础数据类型的访问和读写是具备原子性的.如果应用场景需要一个更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock与unlock操作直接开放给用户使用,但是却提供了更

java并发特性:原子性、可见性、有序性

要想并发程序正确地执行,必须要保证原子性.可见性以及有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 1.原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并发的情况下,就不会出现变量被修改的情况 比如 a=0:(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作.再比如:a++: 这个操作实际是a = a + 1:是可分割的,所以

Java并发编程三个性质:原子性、可见性、有序性

并发编程 并发程序要正确地执行,必须要保证其具备原子性.可见性以及有序性:只要有一个没有被保证,就有可能会导致程序运行不正确 线程不安全在编译.测试甚至上线使用时,并不一定能发现,因为受到当时的CPU调度顺序,线程个数.指令重排的影响,偶然触发 线程安全的定义 比如说一个类,不论通过怎样的调度执行顺序,并且调用处不用对其进行同步操作,其都能表现出正确的行为,则这个类就是线程安全的 并发编程三个概念 原子性: 一个操作或多个操作要么全部执行且执行过程不被中断,要么不执行 可见性: 多个线程修改同一

并发编程之原子性、可见性、有序性的简单理解

并发程序正确地执行,必须要保证原子性.可见性以及有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行. 可见性:当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值. 有序性:程序执行的顺序按照代码的先后顺序执行. 对于单线程,在执行代码时jvm会进行指令重排序,处理器为了提高效率,可以对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证保

聊聊高并发(十九)理解并发编程的几种&quot;性&quot; -- 可见性,有序性,原子性

这篇的主题本应该放在最初的几篇,讨论的是并发编程最基础的几个核心概念,但是这几个概念又牵扯到很多的实际技术,比如Java内存模型,各种锁的实现,volatile的实现,原子变量等等,每一个都可以展开写很多,尤其是Java内存模型,网上已经能够有很几篇不错的文章,暂时不想重复造轮子,这里推荐几篇Jave内存模型的资料: 1. JSR-133 FAQ 2. JSR-133 Cookbook 3. Synchronization and Java Memory Model 4. 深入理解Java内存模

聊聊高并发(十九)理解并发编程的几种&amp;quot;性&amp;quot; -- 可见性,有序性,原子性

这篇的主题本应该放在最初的几篇.讨论的是并发编程最基础的几个核心概念.可是这几个概念又牵扯到非常多的实际技术.比方Java内存模型.各种锁的实现,volatile的实现.原子变量等等,每个都可以展开写非常多,尤其是Java内存模型,网上已经可以有非常几篇不错的文章,临时不想反复造轮子.这里推荐几篇Jave内存模型的资料: 1. JSR-133 FAQ 2. JSR-133 Cookbook 3. Synchronization and Java Memory Model 4. 深入理解Java内

并发研究之可见性、有序性、原子性

并发研究之可见性.有序性.原子性 原子性 原子性(Atomicity):一个操作是不可中断的,要么全部执行成功要么全部执行失败. 在典型的转账场景中,入账和出账操作要么同时成功,要么同时失败.其操作过程是一个整体不可分割,这种操作就是原子性的操作. int a = 1;//1 a++;//2 int b = a;//3 通过上面的伪代码来解释下什么是原子性操作.首先java内存模型中定义了8中操作都是原子的,不可再分的(java内存模型-内存间的八种同步操作).而八种操作执行期间可能会被插入其他