美文网首页JavaJava 杂谈程序员专栏
快速鸟瞰并发编程, 呕心沥血整理的架构技术【1】

快速鸟瞰并发编程, 呕心沥血整理的架构技术【1】

作者: 享学课堂 | 来源:发表于2019-07-25 14:39 被阅读2次

    作者:享学课堂James老师

    转载请声明出处!

    Java程序员,你必须得知道并发编程概念

    大家好,我是享学课堂风骚走位的James, 并发编程作为Java编程的核心灵魂, 不管在面试还是在工作中,都是非常重要的, 花了不少时间我整理出了并发编程的一个核心知识, 希望能够帮助更多的Java开发人员,在工作中合理使用多线程, 让你的代码更高效, 更直观。大概分为以下板块。

    目录

    ► 简介

    ► 概念

    ►Java内存模型:Happens-before 关系

    ► 标准同步功能

    ► 安全发布

    ► 对象不可变

    ► 线程Thread类

    ► 线程的活跃度:死锁与活锁

    ► java.util.concurrent包

    第1节 简介

    所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。这段是从百度百科找到的解释, 而我的解释, 你所写的任何一行代码, 它的执行都是用线程来运行的, 也就是说并发编程直接决定了你系统性能运行情况, 能让你的系统起飞, 也能让你的系统性能变成蜗牛。

    第2节 概念

    从JDK的最低版本开始,Java就支持了线程和锁等关键并发概念。那么这些概念务必记到你脑海深处,哈哈.

    前提条件

    当多个线程对共享资源执行一系列操作时, 它们会对竞争共享资源, 每个线程的操作顺序不一样, 会导致多个不可预期的操作结果。比如以下的代码就为非线程安全的,其中 value可以多次初始化,因为 if(value==null)做了null 判断, 然后初始化的, 延迟初始化的字段不是原子的

    class  JamesLazy <T>  {
        private  volatile T value;
        T get() {
            if (value == null)//这里做了null判断, 延迟初始化的字段它不是原子的
            value = initialize();
            return value;
        }
    }
    
    

    数据竞争

    当两个或多个线程在没有同步的情况下尝试访问相同的非final变量时,就会发生数据竞争。不使用同步可能导致你的所有操作对其它线程是不可见的,因此可以读取过时数据,但若反过来可能会产生无限循环,损坏的数据结构或不准确的计算等后果。此代码可能会导致无限循环,因为读者线程可能永远不会观察到写入器线程所做的更改:

    class  JamesWaiter  implements  Runnable {
        private  Boolean shouldFinish;
        void finish() {
            shouldFinish = true;
        }
        public  void run() {
            long iteration = 0;
            while (!shouldFinish) {
                iteration++;
            }
            System.out.println("完成后的结果: " + iteration);
        }
    }
    class  JamesDataRace {
        public  static  void main(String[] args) throws  InterruptedException {
            JamesWaiter waiter = new  JamesWaiter();
            Thread waiterThread = new  Thread(waiter);
            waiterThread.start();
            waiter.finish();
            waiterThread.join();
        }
    }
    

    运行结果为:

    第3节 Java内存模型:Happens-before 关系

    Java内存模型是根据读取和写入字段以及在监听器上同步等操作定义的。可以通过Happens-before关系对操作进行排序,一般用于推断线程何时看到另一个线程的操作结果,以及用来分析同步的程序构成状况。

    在下图中,Thread 1的 ActionX操作在 ActionY操作之前就调用了,因此在 Thread2中所有操作 ActionY的右侧业务操作时, 将会看到Thread 1中Action X前的所有操作。

    第4节 标准同步功能

    synchronized关键字

    synchronized关键字用于防止不同的线程同时执行相同的代码块, 其实就是指这个代码块只被一个线程执行。当线程A获得synchronized锁后,只有线程A访问synchronized代码块,是一种独占式模式操作, 例如: 13号技师被王根基同学带进屋后,王根基同学在门上挂了把锁, 其它线程得等待, 保证了13号技师只能与王根基(线程)进行业务操作 ,不难看出这个操作原子操作(只有王根基线程操作13号技师)。此外,它保证其它线程在获取相同的锁之后将观察正在操作线程的结果,何时释放锁。

    class  JamesAtomicOperation {
        private  int counter0 ;
        private  int counter1 ;
        void increment(){
            synchronized(this){
                counter0 ++ ;
                counter1 ++ ;
            }
        }
    }
    

    synchronized关键字可以在方法级来指定。

    锁是可重入的,因此如果线程已经拥有锁,可以再次成功获取它。

    class  JamesReentrantcy {
        synchronized  void doAll(){
            doFirst();
            doSecond();
        }
        synchronized  void doFirst(){
            System.out.println(“第一次操作成功。”);
        }
        synchronized  void doSecond(){
            System.out.println(“第二次操作成功。”);
        }
    }
    

    等待/通知

    wait/notify/notifyAll方法在 Object类中声明。wait的作用可以使线程状态变成 WAITINGTIMED_WAITING(如果已等待超时)状态。为了唤醒一个线程,可以执行以下任何操作:

    • 另一个线程调用 notify,唤醒在监视器上等待的任意线程。

    • 另一个线程调用 notifyAll,唤醒监视器上等待的所有线程。

    • 如果调用 Thread#interrupt。在这种情况下,会抛出 InterruptedException

    最常见的模式是条件循环:

    class  JamesConditionLoop {
        private  Boolean condition;
        synchronized  void waitForCondition()throws  InterruptedException {
            while(!condition){
                wait();
            }
        }
        synchronized  void satisfCondition(){
            condition  = true ;
            notifyAll();
        }
    }
    
    • 同志们请记住,如果 wait/notify/notifyAll要在你的对象上使用,需要首先获取此对象锁。

    • 总是在一个循环中等待检查正在等待的条件 - 如果另一个线程在等待开始之前满足条件,这就解决了时间问题。此外,它还可以保护您的代码免受可能(并且确实发生)的虚假唤醒。

    • 在打电话之前,请务必确保您满足等待条件 notify/notifyAll。如果不这样做将导致通知,但没有线程能够逃脱其等待循环。

    volatile关键字

    volatile解决了多线程之间的资源可见性问题,有这么一层关系大家需要知道:对某个volatile字段的写操作happens-before后续对同一个volatile字段的读操作,比如线程1写入了volatile变量v(这里和后续的“变量”都指的是对象的字段、类字段和数组元素),接着线程2读取了v,那么,线程1写入v及之前的写操作都对线程2可见(线程1和线程2可以是同一个线程)。因此,它保证字段的任何后续读取都将看到由最近写入设置的值。

    class  JamesVolatileFlag  implements  Runnable {
        private  volatile  Boolean shouldStop;
        public  void run() {
            while (!shouldStop) {
                // TODO业务代码
            }
            System.out.println("停止.");
        }
        void stop() {
            shouldStop = true;
        }
        public  static  void main(String[] args) throws  InterruptedException {
            JamesVolatileFlag flag = new  JamesVolatileFlag();
            Thread thread = new  Thread(flag);
            thread.start();
            flag.stop();
            thread.join();
        }
    }
    

    原子操作

    java.util.concurrent.atomic包路径下的一些类以无锁方式支持单个值上的原子复合操作,类似于volatile。

    使用AtomicXXX类,可以实现原子 check-then-act操作:

    class  JamesCheckThenAct {
        private  final  AtomicReference<String> value = new  AtomicReference<>();
        void initialize() {
            if (value.compareAndSet(null, "value")) {
                System.out.println("仅初始化一次.");
            }
        }
    }
    
    

    jdk8中新增的 AtomicIntegerAtomicLong具有 increment/decrement(自增自减)的原子操作:

    class  JamesIncrement {
        private  final  AtomicInteger state = new  AtomicInteger();
        void advance() {
            int oldState = state.getAndIncrement();
            System.out.println("Advanced: '" + oldState + "' -> '" + (oldState + 1) + "'.");
        }
    }
    

    若计数器不需要原子操作来获取其值,请考虑使用 LongAdder而不是 AtomicLong/ AtomicInteger类。LongAdder其实就是把一个变量分解为多个变量,让同样多的线程去竞争多个资源,性能问题就可以解决了,因此它在高争用下表现更好。

    ThreadLocal

    从概念上讲,ThreadLocal就好像在每个Thread中都有一个具有自己版本的变量。ThreadLocal通常用于存储每个线程的值,如“当前事务”或其他资源。此外,它们还用于维护每线程计数器,统计信息或ID生成器。

    class  JamesTransactionManager {
        private  final  ThreadLocal<Transaction> currentTransaction = ThreadLocal.withInitial(NullTransaction::new);
        Transaction currentTransaction() {
            Transaction current = currentTransaction.get();
            if (current.isNull()) {
                current = new  TransactionImpl();
                currentTransaction.set(current);
            }
            return current;
        }
    }
    

    好了, 第1篇就到这里,希望大家持续关注更新…………

    相关文章

      网友评论

        本文标题:快速鸟瞰并发编程, 呕心沥血整理的架构技术【1】

        本文链接:https://www.haomeiwen.com/subject/ogvcrctx.html