JAVA并发编程的学习之路

让人想犯罪 __ 提交于 2020-03-08 12:57:07

一Java内存模型

1 Java内存模型(JMM):为了屏蔽各种硬件和操作系统的内存访问差异, 以实现java程序在任何平台下有相同的并发效果,Java虚拟机规范中定义了java内存模型。 它规范了java虚拟机与计算机内存是如何协调工作的 ,规定了一个线程何时可以看到被其它线程修改过的共享变量的值,以及如何在必须时同步的访问共享变量。
2 同步8种操作:lock、unlock、read、load、use、assign、store、write
lock:作用与主内存的变量,把变量设成一个线程独占的状态
unlock:作用与主内存的变量,把锁定的线程释放
read:作用与主内存的变量,把一个变量的值,输送到工作内存中
load:作用与工作内存的变量,把read从主内存读入的变量放入工作内存的副本中
use:作用与工作内存的变量,把工作内存中变量的值传给执行引擎,每当虚拟机遇到一个需要用到这个值的字节码指令时执行的操作
assign(赋值):作用与工作内存的变量, 把一个从执行引擎获取到的值赋给工作变量,每当虚拟机遇到一个需要赋值的字节码指令时执行的操作
store:作用与工作内存的变量,把工作内存中的变量值传入到主内存
write:作用与主内存的变量,把store传入过来的变量存入主内存

二线程安全性

1 线程安全性:当多个线程访问某个类时,不管这些线程采用何种调度方式,并且在主调代码中,不需要任何的同步操作,这个类都能表现出正常的结果,则称这个类是线程安全的
2线程安全问题
(1)原子性:一个或多个操作在CPU执行过程中不被中断的特性,线程切换带来原子性问题,可以通过atomic原子类(解决i++)、synchronized、lock解决
(2)可见性:一个线程对共享变量的修改,另一个线程对共享变量的修改另一个线程可以立刻看到。共享变量的更新没有及时写到缓存里可以带来可见性问题,可以通过volatile、synchronized、lock解决
volatile:当对volatile变量进行写操作时,JVM会向处理器发送一条lock前缀指令,将这个缓存中的变量写回到主内存中。一个处理器的缓存写到主内存中会导致其他处理器的缓存失效。
synchronized:JMM对synchronized有两条规定,线程解锁前必须把共享变量的最新值刷新到主存,线程加锁时清空工作内存中共享变量的值,从而时用共享变量时需要从主内存获取。(加锁与解锁是同一把锁)。
lock:ReetrantLock是AQS的子类,而AQS中的state是volitale的

(3)有序性:程序按照代码的先后顺序执行,编译优化带来有序性问题,就是系统为了提高程序运行效率,可能会进行指令重排(对于单线程来说,不论怎么重排,语义不变,结果不变)。可以通过Happens-Before规则解决
volatile对于有序性的解决:内存屏障(针对处理器)和禁止指令重排(针对编译器)
内存屏障:volatile写前加storestore,写后加storeload,loadload读前加,读后加loadstore

三JUC包的使用

AQS:用于构建锁和实现同步器的基础框架
AQS组件:CountDownLatch、Semaphore、CyclicBarrier、ReentrantLock
AQS原理:基于FIFO队列实现,内部是Node节点包括线程和一个等待状态waitStatus。线程会尝试获取锁,如果失败则包装成Node节点加入同步队列。如果此节点是头节点的直接后继则会不断尝试获取锁,如果失败则被阻塞。而持有锁的线程在释放锁时会唤醒其后续节点中的线程。另外AQS内部有一个volatile修饰的共享变量state来记录锁的状态,0未锁定,1锁定,其他线程获取锁会失败,同一个线程多次调用会累加,实现可重入锁。有getState、setState、compareAndSetStatus方法操作这个变量。AQS提供了共享锁和排他锁。AQS主要的使用方式是继承,子类通过继承同步器,并实现他的抽象方法来管理同步状态

1工具类
1.1 CountDownLatch:调用await()的线程会一直处于阻塞状态,直到其他线程调用countDown()将计数器的值减为0时,所有因await()而阻塞的线程可以继续执行。用于程序需要等待每个过程执行完后才能继续向下执行的情况,比如并行计算,把父任务拆成多个子任务,子任务执行完汇总结果才能继续执行;只能用一次

1.2Semaphore:适用于只能提供有限资源,能控制并发访问的线程个数,acquire()获取锁、release()释放锁、acquire()获取许可,拿不到就放弃,可以指定等待时间

1.3CyclicBarrier:多个线程相互等待,线程调用awaiting()进入等待状态,技技术器加一,直到到达之前设置的值,等待状态的线程被唤醒,继续执行后续操作。可以使用reset方法重置,循环使用

2原子类AtomicXXX
2.1原理:内部有一个unsafe类的对象,unsafe里提供了许多getAndXXX方法来对值进行改变,getAndXXX方法里调用了本地compareAndSwapXXX来对值进行改变,此类方法是通过CAS思想和自旋实现的

 public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5}

compareAndSwapXXX意思时时对象var1在var2的偏移量处值是否和期望值var5相同,如果相同则把var1在var2的偏移量处值赋为var4
*ABA问题:通过AtomicStampedReference可以解决,该类是一个带有时间戳的类
3锁
3.1 Lock 手动实现ReentrantLock、ReentrantReadWriteLock(悲观读),内部类sync继承自 AbstractQueuedSynchronizer
3.2Condition:针对Lock提供了线程间通信方式await()、signall(),实现类ConditionObject是AbstractQueuedSynchronizer的内部类
3.3synchronized 依赖jvm
3.4volatile:状态标识、双重检测单例模式

4并发容器
4.1BlockingQueue:可实现生产者消费者模型
在这里插入图片描述
4.2CopyOnWriteArrayList
4.3CopyOnWriteArraySet
4.4ConcurrentSkipListSet
4.5ConcurrentHashMap
5线程池
1Executors工厂可以创建线程池,ThreadPoolExecutor可以自定义线程池
2Callable:创建线程的接口,与Runnable不同的是,可以有返回值
3Future:用于获取用Callable创建的线程调用call返回的结果
4FutureTask:继承自Runnable和Future,可以直接创建线程并获取结果

四其他

1JIT编译:运行时需要代码时,将中间语言转换为机器码的编译。
2不可变对象
(1)Collections,unmodifiableXXX()
(2)Guava.ImmutableXX()
3Fock/Join(java7):用于 并行计算,把大任务分成多个子任务,各个子任务放在独立的双端队列中,最后将汇总结果,类似于map/reduce的思想。利用工作窃取算法
特点:不能抛出检查异常
不能io
只能用fock和join作为同步机制,不能使用其他

五多线程并发实践总结

1使用本地变量
2使用不可变类
3使用线程池而不是new Thread()
4最小化锁作用范围
5使用BlockQueue实现生产者消费者
6使用并发集合而不是加了锁的同步集合
7使用Semaphore创建有界访问
8尽量不要使用wait和notify
9尽量不要使用静态变量

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!