zoukankan      html  css  js  c++  java
  • Java并发编程(一)

    Java并发编程(一) - 基础知识

    之前看《Thinking In Java》时,并发讲解的挺多的,自己算是初步了解了并发。但是其讲解的不深入,自己感觉其讲解的不够好。后来自己想再学一学并发,买了《Java并发编程实战》,看了一下讲的好基础、好多的理论,而且自我感觉讲的逻辑性不强。最后,买了本《Java并发编程的艺术》看,这本书挺好的,逻辑性非常强。

    1. 概述


    本篇文章主要内容来自《Java并发编程的艺术》,其讲解的比较深入,自己也有许多不懂的地方,然后自己主要把它讲解的提炼出来。本章是Java并发编程的基础知识,了解后能够对Java并发有一些基本的了解,其中许多深入计算机系统层面的知识我都滤过。想要深入而具体的了解,请见《Java并发编程的艺术》。

    2. 基本知识点


    内存可见性:当一个线程修改一个共享变量(存放在堆中:如实例域、静态域、数组等)时,另一个线程总能读到修改后的值。

    原子性(原子操作):不可以被中断的一个或一系列操作。

    重排序:在执行程序时,为了提高性能,编译器和处理器常常对指令做重排序。

    3. Java内存模型(JMM)


    3.1 Java的并发编程采用共享内存模型
    通信(指线程之间以何种机制交换信息):线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。

    同步(指程序中用于控制不同线程间操作发生相对顺序的机制):程序员必须显示的指定某个方法或某段代码需要在线程之间互斥执行。

    3.2 JMM定义了线程与主内存之间的抽象关系
    线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存存储了该线程以读/写共享变量的副本。所以,JMM通过控制主内存与每个线程的本地内容之间的交互,来为Java程序提供内存可见性。
    (注:本地内存是一个抽象概念,并不真实存在,它包含缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。)

    3.3 JMM不保证对64位的long类型与double类型的写操作具有原子性,但读操作具有原子性
    原因:JMM对64位的写操作会拆分为两个32位的写操作来执行。

    4. happens-before


    4.1 概念:Java中阐述操作之间的内存可见性
    在JMM中,如果一个操作的结果需要对另一个操作可见,那么这两个操作之间存在happens-before关系。注:happens-before仅仅要对前一个操作(执行的结果)对后一操作可见,并不意味着操作顺序。

    4.2 happens-before的规则:

    • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。注:JMM中只要求对该线程的结果不会变化,所以只要最后结果没有变化,JMM容许操作系统的重排序。
    • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
    • volatile变量规则:对一个volatile域的写入,happens-before于任意后续对这个volatile域的读。
    • happens-before具有传递性。
    • 线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。
    • 线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
    • 线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。
    • 对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。

    5. volatile


    5.1 volatile特性

    • 内存可见性:对于一个volatile变量的读,总能看到(任何线程)对这个volatile变量最后的写入。
    • 原子性:对于任意单个volatile变量的读/写具有原子性(例如volatile=*),但类似于volatile++这种复合操作不具有原子性。
    • volatile变量可以多线程的读,但只能单线程写。
    • volatile变量的写操作优先于读操作。

    5.2 volatile写-读建立的happens-before关系
    volatile变量的写-读可以实现线程之间的通信(隐式)。
    如下代码:

    class VolatileExample {
        private int a = 0;
        private volatile boolean flag = false;
    
        void write() {
            this.a = 1;		// 1
            this.flag = true;	// 2
        }
    
        void read() {
            while (flag) {		// 3
                int i = a;		// 4
                ...
            }
        }
    }
    

    如果线程A执行write()方法后,线程B执行read()方法。根据happens-before规则:B线程总能够进入循环,并且获得的a的值为1。
    程序顺序规则:1 happens-before 2;3 happens-before 4
    volatile变量规则:2 happens-before 3
    传递规则:1 happens-before 4

    5.2 volatile写-读的内存意义

    • 写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中,并通知接下来要读取这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
    • 读一个volatile变量,实质上该线程接受到之前某个线程发出的修改消息,JMM会把该线程对应的本地内存置为无效,线程接下来会从主内存中读取共享变量。

    6. CAS


    6.1 CAS定义
    CAS:Compare And Swap(比较与交换),它会比较预期值与当前内存值是否相等,如果相等则把内存值变成期望值,否则不变。

    6.2 CAS原理
    Java中的CAS是通过JNI(Java Native Interface)调用本地方法实现的。本地调用在sun.misc.Unsafe类中的compareAndSwap的一系列方法,该方法通过使用本地方法来实现CAS的原子性。所以Java中CAS具有volatile读写的内存意义。

    6.3 CAS应用
    Java中有许多并发都是通过CAS操作来实现的,如Aomic类。Atomic类通过CAS来实现原子性,volatile来实现内存可见性。具体如下:

    public class AtomicInteger extends Number implements java.io.Serializable {
        private volatile int value;
        
        public final int get() {
            return value;
        }
        
        public final void set(int newValue) {
            value = newValue;
        }
    
        public final int getAndIncrement() {
            return unsafe.getAndAddInt(this, valueOffset, 1);
        }
    }
    

    从上面截图的一部分AtomicInteger的实现,我们可以看出由于volatile对于volatile++这种复合操作没有原子性,所以其使用了CAS来实现原子性。

    7. 锁


    7.1 锁与volatile的内存意义基本相似(隐式通信)

    • 锁的释放与volatile变量的写有相同的内存意义。
    • 锁的获取与volatile变量的读有相同的内存意义。

    7.2 锁能够让临界区互斥执行(显式同步)

    7.3 锁与volatile变量的比较

    • volatile仅仅能够保证单个volatile变量的读/写具有原子性,而锁能够保证整个临界区具有原子性。
    • volatile性能比锁好,而功能不如锁。

    注:Lock锁的实现使用到了volatile与CAS来实现的,具体后面会讲。

    8. final域


    7.1 内存意义:
    只要final对象时正确构造的,那么不需要使用同步(锁与volatile)就可以保证任何线程都能够看都该对象的final域在构造函数中被初始化的值。

    9. Concurrent包的实现


    9.1 实现原理
    由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
    A线程写volatile变量,随后B线程读这个volatile变量。
    A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
    A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
    A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

    9.2 实现机制
    如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

    1. 首先,声明共享变量为volatile
    2. 然后,使用CAS的原子条件更新来实现线程之间的同步
    3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信

    整体实现如下图:
    enter description here

    注:AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的

    10.应用


    8.1 单例模式中可以使用双重检查锁机制来创建单例
    代码如下:

    class Singleton {
        private volatile static Singleton instance;	// 1
        
        private Singleton() {}
        
        public static Singleton getInstance() {
            if (instance == null) {				// 2
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();		// 3
                    }
                }
            }
            return instance;
        }
    }
    

    注意:实例引用必须是volatile变量。如果不是,在运行到2时读取到instance不为null,但是可能该instance所指向的对象还没有完成初始化。原因:创建对象时的重排序,自己不太清楚,具体可以参见《Java并发编程的艺术》。

    8.2 单例模式中基于类初始化的获取单例(内部类)
    代码如下:

    class Singleton {
        private Singleton() {}
        
        public static Singleton getInstance() {
            return SingletonHolder.instance;  // 这里才会导致SingletonHolder类的初始化
        }
        
        private static class SingletonHolder{
            static Singleton instance = new Singleton();
        }
    }
    

    优点:只有在第一次调用getInstance()方法时才会正式加载SingletonHolder类,也就是才会创建对象。

    线程安全的原因:在类的初始化时期,JVM会获取一个锁,该锁可以同步多个线程对同一个类的初始化。

    11.References


    《Java并发编程的艺术》

  • 相关阅读:
    Python 爬虫的工具列表
    使用rabbitmq手动确认消息的,定时获取队列消息实现
    redis订阅发布简单实现
    ubuntu下打开html页面
    关系数据库基本术语
    事务的基本概念,附图示
    oracle 一对多数据分页查询筛选
    一个在linux环境执行io操作的bug
    再springMVC中自定义文件上传处理解决与原spring中MultipartResolve冲突问题
    oracle存储过程删除树状结构的表数据
  • 原文地址:https://www.cnblogs.com/maying3010/p/6924710.html
Copyright © 2011-2022 走看看