• 多线程问题


    线程安全

    多线程主要是为了提高我们应用程序的使用率。但同时,这会给我们带来很多安全问题

    因为在多线程的环境下,线程是交替执行的,一般他们会使用多个线程执行相同的代码。如果在此相同的代码里边有着共享的变量,或者一些组合操作,我们想要的正确结果就很容易出现了问题

    性能问题

    使用多线程我们的目的就是为了提高应用程序的使用率,但是如果多线程的代码没有好好设计的话,那未必会提高效率。反而降低了效率,甚至会造成死锁

    学习使用哪种同步机制来实现线程安全,并且性能是提高了而不是降低了~

    解决线程安全

    一般会有下面这么几种办法来实现线程安全问题:

    • 无状态(没有共享变量)

    • 使用final使该引用变量不可变(如果该对象引用也引用了其他的对象,那么无论是发布或者使用时都需要加锁)

    • 加锁(内置锁,显示Lock锁)

    • 使用JDK为我们提供的类来实现线程安全(此部分的类就很多了)

      • 原子性(就比如上面的count++操作,可以使用AtomicLong来实现原子性,那么在增加的时候就不会出差错了!)

      • 容器(ConcurrentHashMap等等…)

      • ……

    • …等等

    原子性

    在多线程中很多时候都是因为某个操作不是原子性的,使数据混乱出错。如果操作的数据是原子性的,那么就可以很大程度上避免了线程安全问题了!

    • count++,先读取,后自增,再赋值。如果该操作是原子性的,那么就可以说线程安全了(因为没有中间的三部环节,一步到位【原子性】~

    原子性就是执行某一个操作是不可分割的  比如上面所说的count++操作,它就不是一个原子性的操作,它是分成了三个步骤的来实现这个操作的~ - JDK中有atomic包提供给我们实现原子性操作

    Atomic

    原子变量类在java.util.concurrent.atomic包下,总体来看有这么多个

    • 基本类型:

      • AtomicBoolean:布尔型

      • AtomicInteger:整型

      • AtomicLong:长整型

    • 数组:

      • AtomicIntegerArray:数组里的整型

      • AtomicLongArray:数组里的长整型

      • AtomicReferenceArray:数组里的引用类型

    • 引用类型:

      • AtomicReference:引用类型

      • AtomicStampedReference:带有版本号的引用类型

      • AtomicMarkableReference:带有标记位的引用类型

    • 对象的属性:

      • AtomicIntegerFieldUpdater:对象的属性是整型

      • AtomicLongFieldUpdater:对象的属性是长整型

      • AtomicReferenceFieldUpdater:对象的属性是引用类型

    • JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder

      • 是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。

     

    可见性 volatile

    对于可见性,Java提供了一个关键字:volatile给我们使用~

    • 我们可以简单认为:volatile是一种轻量级的同步机制

    volatile经典总结:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性

    我们将其拆开来解释一下:

    • 保证该变量对所有线程的可见性

    • 在多线程的环境下:当这个变量修改时,所有的线程都会知道该变量被修改了,也就是所谓的“可见性”

    • 不保证原子性

    • 修改变量(赋值)实质上是在JVM中分了好几步,而在这几步内(从装载变量到修改),它是不安全的

    使用了volatile修饰的变量保证了三点

    • 一旦你完成写入,任何访问这个字段的线程将会得到最新的值

    • 在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。

    • volatile可以防止重排序(重排序指的就是:程序执行的时候,CPU、编译器可能会对执行顺序做一些调整,导致执行的顺序并不是从上往下的。从而出现了一些意想不到的效果)。而如果声明了volatile,那么CPU、编译器就会知道这个变量是共享的,不会被缓存在寄存器或者其他不可见的地方。

    一般来说,volatile大多用于标志位上(判断操作),满足下面的条件才应该使用volatile修饰变量:

    • 修改变量时不依赖变量的当前值(因为volatile是不保证原子性的)

    • 该变量不会纳入到不变性条件中(该变量是可变的)

    • 在访问变量的时候不需要加锁(加锁就没必要使用volatile这种轻量级同步机制了)

    线程封闭

    在多线程的环境下,只要不使用成员变量(不共享数据),那么就不会出现线程安全的问题了。

    在方法上操作,只要我们保证不要在栈(方法)上发布对象(每个变量的作用域仅仅停留在当前的方法上),那么我们的线程就是安全的

    在线程封闭上还有另一种方法:ThreadLocal

    使用这个类的API就可以保证每个线程自己独占一个变量

    不变性

    final仅仅是不能修改该变量的引用,但是引用里边的数据是可以改的!

    就好像HashMap,用final修饰了。但是它仅仅保证了该对象引用hashMap变量所指向是不可变的,但是hashMap内部的数据是可变的,也就是说:可以add,remove等等操作到集合中。因此,仅仅只能够说明hashMap是一个不可变的对象引用

    • 把HashMap中的对象也设计成是一个线程安全的类

    要想将对象设计成不可变对象,那么要满足下面三个条件:

    • 对象创建后状态就不能修改

    • 对象所有的域都是final修饰的

    • 对象是正确创建的(没有this引用逸出)

  • 相关阅读:
    Redis 缓存 + Spring 的集成示例
    ETCD相关介绍--整体概念及原理方面
    SpringCloud微框架系列整体模块梳理
    win7如何修改磁盘驱动器号,怎么修改磁盘名称
    Android ListView中子控件的状态保存以及点击子控件改变子控件状态
    Android 自己动手写ListView学习其原理 3 ItemClick,ItemLongClick,View复用
    点击itemView选中checkbox
    Android-RecyclerView-Item点击事件设置
    onItemClickListener监听的整个item的点击。如何只监听那个framelayout的点击 onItemClickListener监听的整个item的点击。如何只监听那个framelayout的点击
    listView中setOnItemClickListener和getSelectedItemPosition()取不到position问题
  • 原文地址:https://www.cnblogs.com/yjh1995/p/13514642.html
Copyright © 2020-2023  润新知