网站建设语言,网站如何知道是谁做的呢,高中生做网站网页,e龙岩官网下载并发编程并发编程Java内存模型#xff08;JMM#xff09;并发编程核心问题—可见性、原子性、有序性volatile关键字原子性原子类CAS(Compare-And-Swap 比较并交换)ABA问题Java中的锁乐观锁和悲观锁可重入锁读写锁分段锁自旋锁共享锁/独占锁公平锁/非公平锁偏向锁/轻量级锁/重…
并发编程并发编程Java内存模型JMM并发编程核心问题—可见性、原子性、有序性volatile关键字原子性原子类CAS(Compare-And-Swap 比较并交换)ABA问题Java中的锁乐观锁和悲观锁可重入锁读写锁分段锁自旋锁共享锁/独占锁公平锁/非公平锁偏向锁/轻量级锁/重量级锁偏向锁状轻量级锁重量级锁对象结构sychronized锁实现AQSReentrantLock锁实现JUC常用类ConcurrentHashMapCopyOnWriteArrayListCopyOnWriteArrayList辅助类CountDownLatch对象引用强引用软引用SoftReference内存不足即回收弱引用Weak Reference发现即回收虚引用Phantom Reference对象回收跟踪线程池**TheadPollExecutor**类构造器中各个参数的含义线程池的执行线程池中的队列线程池的拒绝策略关闭线程池ThreadLocalThreadLocal 内存泄漏问题并发编程
并行同一个节点同时发生
并发在一段时间内多个事件交替执行
并发编程:在例如买票、抢购等场景下有大量请求访问同一资源会出现线程安全的问题所以需要通过编程来解决让多个线程依次访问资源称为并发编程
Java内存模型JMM
java内存模型是java虚拟机规范的一种工作模式
JMM将内存分为主内存和工作内存。变量数据存储在主内存中线程在操作变量时会将主内存中的数据复制到工作内存,在工作内存中操作完成后再写回主内存 并发编程核心问题—可见性、原子性、有序性
基于java内存模型的设计多线程操作一些共享数据时会出现三个问题
不可见性多个线程分别对共享数据进行操作彼此之间不可见操作结束写回主内存可能会出现问题
无序性为了性能对一些代码执行的执行顺序进行重排以提高速度
非原子性线程切换带来的原子性问题
volatile关键字
共享变量被volatile修饰以后
1.共享变量被一个线程修改后对其他线程立即可见
2.在执行过程中不会被重排
3.不能保证对变量操作的原子性
volatile底层实现原理
使用Memory Barrier(内存屏障)内存屏障是一条指令它可以对编译器和处理器的指令重排做出一i的那个的限制。
有序性实现:volatile修饰的变量在操作前添加内存屏障不让他的指令干扰
可见性实现主要通过Lock前缀指令MESI缓存一致性协议来实现。操作volatile修饰的变量时JVM会发送Lock前缀指令给CPU,CPU在执行完操作后会立即将新值刷新到内存其他CPU都会对总线嗅探看自己本地缓存的数据是否被修改如果修改了就会将修改的数据存到本地缓存中主内存就h会加载最新的值。
原子性
通过加锁的方式让线程互斥执行来保证一次只有一个线程执行
锁synchronized关键字是锁的一种实现。synchronized一定能保证原子性也能够保证可见性和有序性。
原子变量JUC(java.util.concurrent包)中的locks包和atmic包可以解决原子性问题
加锁是一种阻塞式方式实现原子变量是非阻塞方式实现。
原子类
原子类的原子性是通过volatileCAS实现原子操作适合与低并发的条件下
CAS(Compare-And-Swap 比较并交换)
CAS是乐观锁的一种实现方式采用的是自选锁的思想是一种轻量级的锁机制 底层是通过 Unsafe 类中的 compareAndSwapInt 等方法实现.
CAS包含了三个操作数 内存值 V、预估值 A、更新值 B
过程1.第一次将主内存中的值放到工作内存中作为预期值然后将更新值存入工作内存。
2.将工作内存中的值写入主内存前需要把预期值和主内存的值进行比较。
3.如果主内存的值和预期值相等,将更新值写入主内存如果不相等说明有其他线程修改了主内存的值需要重复上述过程直到主内存的值和预估值相等。 缺点CUP的消耗增加
ABA问题
ABA问题即某个线程将内存值A改为了B再又A改为了A当另一个线程是使用预期值去判断时内存值和与预期值相等无法判断内存值是否发生过变化 解决方式通过使用类添加版本号来避免问题的发生如原先的内存值为A1线程将A1修改为了B2再由B2修改为A3。此时另一个线程使用预期值A1与内存值A3进行比较只需要比较版本号 1 和3.即可发现内存值被更新过。
Java中的锁
java中锁的名词不是全指的是锁。还可以指的是锁的特性、锁的状态、锁的设计
乐观锁和悲观锁
乐观锁认为同一个数据并发的操作是不会发生修改的不加锁的方式实现是没有问题的每次操作前判断CAS是否成立。
悲观锁认为同一个数据并发的操作会发生修改必须加锁
可重入锁
当一个线程获取外层方法的同步锁对象后可以获取到内部其他同步锁
public class Demo{synchronized void setA throws Exception{System.out.println(A);setB();}synchronized void setB throws Exception{System.out.println(B);}
}上面的代码就是一个可重入锁的一个特点如果不是可重入锁setB不会被当前线程执行造成死锁
读写锁
支持读写加锁如果都是读操作那么就不加锁如果存在写操作就会出现操作互斥
读写锁为了防止脏读
private int data;//共享数据private ReadWriteLock rwlnew ReentrantReadWriteLock();public void set(int data){rwl.writeLock().lock();//获取到写锁try {System.out.println(Thread.currentThread().getName()准备写入数据);this.datadata;System.out.println(Thread.currentThread().getName()写入数据this.data);}finally {rwl.writeLock().unlock();//释放写锁}}public void get(){rwl.readLock().lock();//取到读锁try {System.out.println(Thread.currentThread().getName()准备写入数据);System.out.println(Thread.currentThread().getName()写入数据this.data);}finally {rwl.readLock().unlock();//释放读锁}}分段锁
不是锁,是一种锁的实现思想用于将数据分段给每个分段数据加锁提高并发效率
自旋锁
不是锁以自旋的方式进入锁。自旋锁是比较消耗CPU
共享锁/独占锁
共享锁该锁可被多个线程共享。读写锁中的读锁是共享锁
独占锁是指该锁只能被一个线程拥有。Synchronized
公平锁/非公平锁
公平锁是按请求的顺序来获取锁先来后到例如ReentrantLock
非公平锁没有顺序谁抢到谁执行。例如synchronized
偏向锁/轻量级锁/重量级锁
synchronized锁的状态存储在同步锁对象的对象头中的区域Mark Word中存储
锁的状态有四种无锁状态、偏向锁状态、轻量级锁、重量级锁
偏向锁状
代码一直被一个线程访问那么该线程会自动获取锁
轻量级锁
当锁的状态是偏向锁时又有一个线程访问轻量级锁就会升级为轻量级锁其他线程就会通过自旋的方式获取锁不会阻塞。
重量级锁
当锁的状态为轻量级锁时当线程自选到一定的次数就会进入阻塞状态锁状态升级为重量级锁等待操作系统调度。
对象结构
在Hotspot虚拟机中对象在内存中的布局分为三块区域对象头、实例数据和对齐填充synchronized使用的锁对象是存储在java对象头中。 对象头中有一块Mark word用于存储对象自身运行时的数据如哈希码、GC分代年龄、锁状态、线程持有的锁、偏向锁等等。
下面就是对象头的一些信息
sychronized锁实现
Java提供的一种原子性内置锁。synchronized基于进入和退出监视器对象来实现方法同步和代码块同步。
同步方法使用 ACC_SYNCHRONIZED标记是否为同步方法当方法调用时会检查方法是否被标记如果被标记线程进入该方法时需要monitorenter,退出方法时需要monitorexit.
代码块的同步是利用 monitorenter 和 monitorexit 这两个字节码指令
monitorenter指令尝试获取对象的锁如果获取到把锁的计数器加1
monitorexit:将锁计数器减一当计数器为0时锁就被释放
Java 中 synchronized 通过在对象头设置标记达到了获取锁和释放锁的目的。
AQS
AQS抽象同步队列,是JUC中的核心组件其他锁实现的基础
实现原理:
在类中维护一个state变量表示锁是否使用然后还维护一个队列以及获取锁释放锁的方法
当线程创建后先判断state值当state0,没有线程使用当state1线程会去队列等待。
等占有state的线程执行完成将state-1后会唤醒对列中等待的线程head中的下一个结点去获取state
AbstractQueuedSynchronizer 成员
private transient volatile Node head;
private transient volatile Node tail;
/*使用变量 state 表示锁状态,0-锁未被使用,大于 0 锁已被使用
共享变量 state使用 volatile 修饰保证线程可见性
*/
private volatile int state;状态信息通过 getState setState compareAndState来操作
protected final int getState() { //获得锁状态return state;}protected final void setState(int newState) {//设置锁状态state newState;}//使用 CAS 机制设置状态protected final boolean compareAndSetState(int expect, int update) {return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}获取锁的方式有两种
tryAcqurie () :尝试获取锁。
acquire():尝试获取锁获取失败时进入队列等待。直到获取
public final void acquire(int arg) {//tryAcquire获取锁成功,方法结束获取锁失败执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)将线程arg添加到队列中。if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}addWaiter: 尝试获取锁失败后,将当前线程封装到一个 Node 对象中,添加 到队尾,并返回 Node 节点. acquireQueued: 将线程添加到队列后,以自旋的方式去获取锁 release 释放锁 tryRelease: 释放锁,将 state 值进行修改为 0 unparkSuccessor: 唤醒节点的后继者如果存在
AQS的锁模式分为独占和共享
ReentrantLock锁实现
ReentrantLock基于AQS,可以实现公平锁和非公平锁
ReentrantLock有Sync、NonfairSync、FairSync三个内部类他们紧密相关 NonfairSync继承了Sync类实现非公平锁
static final class NonfairSync extends Sync {
//加锁
final void lock() {
//若通过 CAS 设置变量 state 成功就是获取锁成功则将当前线程设置为独占线程。
//若通过 CAS 设置变量 state 失败就是获取锁失败则进入 acquire 方法进行后续处理。if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}//尝试获取锁,无论是否获得都立即返回protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}FairSync 类也继承了 Sync 类实现公平锁
static final class FairSync extends Sync {final void lock() {// 以独占模式获取对象忽略中断acquire(1);//底层实现交由 AbstractQueuedSynchronizer}
}JUC常用类
ConcurrentHashMap
ConcurrentHashMap是线程安全的哈希表在多线程操作中比Hashtable效率高内部使用cassynchronized(分段锁被弃用)。
放弃分段锁的原因浪费内存在运行环境中map中同时进入同一个位置的概率很小分段所反而会浪费更多的时间。
jdk8放弃分段锁使用Node锁。提高了性能并使用CAS操作来保证Node操作的原子性。
过程
put时通过hash找到对应链表后查看是否是第一个Node,如果是直接用cas原则插入。
如果不是则直接用链表第一个Node加synchornized锁。 ConcurrentHashMap和Hashtable一样 不支持存储 null 键和 null 值. 这样是为了消除歧义
不能put null是因为 无法分辨key没找到返回null还是有key值为null,所以不能 null.
不等存储null值是因为当你get(k)获取value时如果获取到null时你无法判断是value值为null,还是这个key还没做过映射
CopyOnWriteArrayList
ArrayList是线程不安全的Vector是线程安全vector读操作和写操作都加了锁实际应用中读操作很频繁且读操作不会修改数据。所以CopyOnWriteArrayList为了提高性能出现。
CopyOnWriteArrayList修改数据流程当list需要被修改时并不直接对原有list进行修改而是对原有数进行拷贝将修改的内容写入副本中修改结束后将修改完的副本替换成原来的数据
CopyOnWriteArrayList
CopyOnWriteArrayList实现基于CopyOnWriteArrayList,不能存储重复元素数据
辅助类CountDownLatch
CountDownLatch底层时通过AQS来完成的一个线程等待其他线程执行完才执行。
过程创建CountDownLatch 对象指定一个线程数量。每当一个线程执行完毕后AQS内部的state-1,当state0时表示所有线程都执行完毕然后在闭锁上等待的线程就可以执行
CountDownLatch countDownLatch new CountDownLatch(6);//设置线程总量for (int i 0; i 6 ; i) {new Thread(()-{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(aaaaaaaaaaaaa);countDownLatch.countDown();}).start();}countDownLatch.await();System.out.println(main线程执行);//最后执行的内容对象引用
强引用
对象由引用指向的如 Object objnew Object();这种情况下永远不会被垃圾回收器回收
软引用、弱引用、虚引用都是用来标记对象的一种状态
软引用SoftReference内存不足即回收
软引用是用来描述一些还有用但非必须的对象如果内存充足的情况下可以保留软引用如果内存不足。经过一次垃圾回收后内存依然不足软引用的对象就会被清除
弱引用Weak Reference发现即回收
弱引用的对象也是描述非必须的对象它只能存活到下一次垃圾回收发生为止当垃圾回收器和工作就会回收掉弱引用的对象
虚引用Phantom Reference对象回收跟踪
虚引用的对象和没有引用几乎是一样的随时都会被垃圾回收器回收虚引用必须和引用队列一起使用。虚引用在创建时必须提供一个引用队列作为参数目的是在这个对象被回收时收到系统通知
线程池
线程池就是事先创建一些线程每次使用时直接获取用完不销毁
Executors提供了常见的线程池创建方法
newSingleThreadExecutor一个单线程的线程池。如果因异常结束会再创建一个新的保证按照提交顺序执行。 newFixedThreadPool创建固定大小的线程池。根据提交的任务逐个增加线程直到最大值保持不变。如果因异常结束会新创建一个线程补充。 newCachedThreadPool创建一个可缓存的线程池。会根据任务自动新增或回收线程
通常情况下不建议直接只用Executors来创建线程池
线程池的优点降低资源消耗提高响应速度节省创建时间
TheadPollExecutor类
Java.uitl.concurrent.ThreadPoolExecutor 类是线程池中最核心的一个类ThreadPoolExecutor 继承了 AbstractExecutorService 类并提供了四个构造器事实上通过观察每个构造器的源码具体实现发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
构造器中各个参数的含义
**corePoolSize**核心线程池的大小默认情况创建线程池线程池中线程数为0当有任务来时就会去创建一个线程执行任务当线程数目达到corePoolSize就会把到达的任务放到缓存队列。prestartAllCoreThreads()或者 prestartCoreThread()方法是在没有任务到来之前就创建corePoolSize个线程或一个线程
**maximumPoolSize**线程池最大线程数。
**keepAliveTime**表示线程没有任务执行时最多保持多长时间停止。只有当线程池中的线程数量大于corePoolSize时keepAliveTime才会起作用直到线程数小于corePoolSize时keepAliveTime才终止。
unit参数 keepAliveTime 的时间单位有 7 种取值
workQueue一个阻塞队列用来存储等待执行的任务这个参数的选择也很重要会对线程池的运行过程产生重大影响 。
**threadFactory**线程工厂主要用来创建线程 **handler**表示当拒绝处理任务时的策略
线程池的执行
创建完成ThreadPoolExecutor,当向线程池提交任务时通常使用 execute方法。
提交执行流程如图 1.如果线程池中的存在的核心线程数小于corePoolSize时线程池会创建一个核心线程去执行任务
2.如果核心线程数已满任务会被放进任务队列workQueue排队执行
3.如果任务队列已满且线程数小于maximumPoolSize时创建一个非核心线程执行提交的任务。
4.如果当前线程数达到maximumPoolSize时。直接采用拒绝策略处理
线程池中的队列
ArrayBlockingQueue是数组实现的有界的阻塞队列,必须给定最大容量
LinkedBlockingQueue基于链表结构的阻塞队列按 FIFO 排序任务容量可以选择进行设置不设置是一个最大长度为 Integer.MAX_VALU
线程池的拒绝策略
构造方法中的RejectedExecutionHandler用于指定线程池的拒绝策略。拒绝策略用于请求任务太多线程池处理不过来的情况。
默认有四种类型
AbortPolicy:直接抛出异常拒绝执行
CallerRunsPolicy:将任务交给提交任务的线程如main方法来执行此任务
DiscardOleddestPolicy:该策略会丢弃等待时间最长的任务也就是最后即将被执行的任务并尝试再次提交当前任务。
DiscardPolicy直接丢弃当前提交的任务,不执行
excute与submit的区别execute 适用于不需要关注返回值的场景submit 方法适用于需要关注返 回值的场景。
关闭线程池
关闭线程池可以调用 shutdownNow 和 shutdown 两个方法来实现
shutdownNow:直接关闭线程池对正在执行的任务全部发出 interrupt()停止执行对还未开始执行的任务全部取消并且返回还没开始的任务列表.
shutdown当我们调用 shutdown 后等待线程池中的任务执行完关闭线程池。
ThreadLocal
ThreadLocal线程变量用来创建一个变量该变量可以被多线程使用且互不干扰。为线程私有。
//创建一个ThreadLocal对象,复制保用来为每个线程会存一份变量,实现线程封闭private static ThreadLocalInteger localNum new ThreadLocalInteger(){Overrideprotected Integer initialValue() {return 0;}};原理ThreadLocal是一个泛型类可以存放任何类型的对象他的类中定义了一个map,用来存放ThreadLocal对象和变量值。ThreadLocal 实 现 了 一 个 ThreadLocalMap 的静态类ThreadLocalMap类中的get(),set()来改变变量值。
ThreadLocal set方法 //set 方法public void set(T value) {//获取当前线程对象Thread t Thread.currentThread();//判断该对象是否已经存入mapThreadLocalMap map getMap(t);if (map ! null)map.set(this, value);elsecreateMap(t, value);}
// 获取 threadLocalmapThreadLocalMap getMap(Thread t) {return t.threadLocals;}
//创建threadLocalmap将ThreadLocal对象和变量值存入map
void createMap(Thread t, T firstValue) {t.inheritableThreadLocals new ThreadLocalMap(this, firstValue);}ThreadLocal get方法 public T get() {//获取当前线程对象Thread t Thread.currentThread();// 获取线程中的ThreadLocalMap对象ThreadLocalMap map getMap(t);if (map ! null) {//获取ThreadLocalMap的Entry对象ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;return result;}}return setInitialValue();}private T setInitialValue() {//设置value为nullT value initialValue();//获取当前线程对象Thread t Thread.currentThread();// 获取线程中的ThreadLocalMap对象ThreadLocalMap map getMap(t);//创建mapif (map ! null)map.set(this, value);elsecreateMap(t, value);return value;}
protected T initialValue() {return null;}ThreadLocal 内存泄漏问题 TreadLocalMap使用ThreadLoal的弱引用为key,如果一个ThreadLocal 不存在外部强引用时Key(ThreadLocal)势必会被 GC 回收这样就会导致ThreadLocalMap 中 key 为 null 而 value 还存在着强引用无法回收造成内存泄露。
所以每次使用完ThreadLocal都调用它的remove()放法清除数据。