Java并发编程

2018-06-18  本文已影响16人  没睡醒的鱼

并发编程基础

CPU多级缓存

那么缓存存在的意义是什么呢?这里就涉及到了局部性原理,局部性原理主要分为以下两类:

乱序执行优化

什么是乱序执行优化:处理器为提高运算速度而做出违背代码原有顺序的优化

Java内存模型(JMM)

java内存模型规范规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值以及在必须时如何同步的访问共享变量


java内存模型抽象结构图

如果线程A与线程B之间要通信的话,线程A把本地内存A中的共享变量刷新到主内存,线程B读取主内存中更新过的共享变量

线程安全性

什么是线程安全性呢?就是说如果你的代码在单线程环境下和多线程环境下的运行结果是一致的,就说明你的代码是线程安全的
线程安全性的三个方面:

原子性

java中提供了Atomic包来实现原子性,接下来来看看Atomic的源码实现

点进AtomicInteger类下,有这样一个方法: 源码 可见底层是采用的CAS操作来实现的原子性。
CAS的ABA问题

ABA问题是指在CAS操作的时候,其他线程将变量的值A改成了B之后又改回了A,本线程使用期望值A与当前变量进行比较的时候发现变量没有变,实际上该值已经被其他线程改变了。
解决方法:每次变量更新的时候,把变量的版本号加一,从而能够解决ABA问题

synchronized原子性(这里做简单描述)
synchronized作用范围和对象

可见性

指一个线程对主内存的修改可以被其他线程及时的观察到
导致线程间不可见的原因:

  1. 使用synchronized,当线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中读取最新的值,实现可见性
  2. volatile通过使用内存屏障和禁止重排序优化来实现可见性

有序性

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

Happens-before原则

发布对象

发布对象:使一个对象能够被当前范围之外的代码所使用
对象溢出:一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程看见

如何安全的发布对象

单例类的实现方式

  1. 懒汉式:

    单例实例在第一次使用时创建,是线程不安全的: 懒汉式
  2. 如何使懒汉式变成线程安全的呢?可以在方法上加上synchronized,但是这样做的缺点是会导致性能降低: 线程安全的懒汉式
  3. 饿汉式,单例实例在类加载的时候创建,是线程安全的;缺点是当构造方法中含有过多的处理时会导致类加载变慢: 饿汉式
  4. 双重锁校验模式,线程不安全: 双重锁校验
    线程不安全的原因是会发生指令重排序,为了解决这个问题,可以用volatile来修饰instance变量,来禁止指令重排: 线程安全的双重锁校验
  5. 枚举类,它是最安全的单例实现方式,相比于懒汉式,更能够保证线程安全性;相比于饿汉式,它在第一次调用时才会初始化,不会造成资源浪费: 枚举实现

线程安全策略

不可变对象

不可变对象需要满足的条件:

final可以修饰类,方法,变量:

  1. 当final修饰类时不能被继承
  2. final修饰的方法不会被继承类修改
  3. final修饰变量,变量初始化之后就不可以改变
    final修饰的变量一定是线程安全的嘛?
    答:不一定,当final修饰基本变量类型时,对象一旦初始化就不能被改变;当final修饰引用变量类型时,对象初始化之后不能指向其他对象,但是值可以改变;所有如果只是引用不能改变但是值可以改变的话就是线程不安全的

线程封闭

概念:就是把对象那个封装到一个线程里,只有一个线程能看到这个对象

线程封闭的实现方式

常见的线程不安全的类与写法

其中线程不安全的写法有:先检查后执行,比如:

if(condition(a)){
  handle(a);
}

因为判断和执行是分开来的,不是原子性的,会引发线程不安全的问题

同步容器

Vector是线程安全的,vector中的方法是synchronized修饰的
Stack是线程安全的,Stack中的方法也是synchronized修饰的
HashTable是线程安全的
Collections.synchronizedxxx()静态方法可以创建线程安全的容器
例如这段代码:

private static ArrayList<Integer> array;
private static List<Integer> list = Collections.synchronizedList(array);

Collections.synchronizedList()把ArrayLIst变成了同步的容器类

并发容器

CopyOnWriteArrayList(对应于ArrayList)

CopyOnWrite容器如何实现线程安全的:

  1. 做写操作时需要拷贝数组就需要消耗内存,如果元素过多可能会导致Young GC或FUll GC
  2. 不能用于实时读的场景,因为拷贝数组,新增元素都需要时间,CopyOnWriteArrayList可以做到最终一致性,但是无法满足实时性的要求,所有它适用于读多写少的场景

特点:

CopyOnWriteArraySet(相对于Set)

线程安全,与CopyOnWriteArrayList类似

ConcurrentHashMap

看知识点,这里不做赘述

JUC之AQS

AQS底层使用双向链表是队列的一种实现,因此也可以当做一个队列

AQS同步组件
CountDownLatch

使用场景:当程序执行需要等待某个条件完成后才能继续执行后续的操作
调用该类await()的线程会一直处于阻塞状态,直到其他线程调用countDown()方法,使当前计数器变为0,每次调用countDown()时会让计数器的值-1,当减到0时,所有因调用await方法处于等待的线程就会继续往下执行

Semaphor

看信号量知识点

CyclicBarrier
示意图

它的功能和countDownLatch类似,他们的区别在于:

ReentrantLock

可重入锁,同一线程,外层函数获得锁之后,内存递归函数仍有获得该锁的机会不受影响,synchronized和reentrantlock都是可重入锁
他们的区别:

*** ReentrantLock可以指定是公平锁还是非公平锁

FutureTask

Future接口:可以得到别的线程方法的返回值
FutureTask:实现了两个接口,Future和Runnable,所有它既可以作为Runnable被线程执行,也可以作为Future作为Callable的返回值
应用:一个很费时的逻辑需要计算并有返回值的时候,同时这个歌值又不是马上需要,那么可以使用上面的两个组合,用另外一个线程计算返回值,而当前线程在使用这个返回值之前可以做其他的操作,等需要这个返回值时再通过Future得到

上一篇 下一篇

猜你喜欢

热点阅读