多线程与高并发

一、线程的六种状态

在这里插入图片描述

1、 初始-NEW

新建线程

2、 运行-RUNNABLE(包含运行中-RUNNING和就绪READY两种状态)

  1. 当线程对象调用了start()方法之后,线程处于【就绪】状态,【就绪】意味着该线程可以执行,但具体啥时候执行将取决于JVM里线程调度器的调度。
  2. 处于【就绪】状态的线程获得了CPU之后,真正开始执行run()方法的线程执行体时,意味着该线程就已经处于【运行】状态。需要注意的是,对于单处理器,一个时刻只能有一个线程处于【运行】状态。对于抢占式策略的系统来说,系统会给每个线程一小段时间处理各自的任务。时间用完之后,系统负责夺回线程占用的资源。下一段时间里,系统会根据一定规则,再次进行调度。

3、 阻塞-BLOCKED

阻塞】状态表示线程正等待监视器锁,而陷入的状态。

4、 等待-WAITTING

进入该状态表示当前线程需要等待其他线程做出一些的特定的动作(通知中断)。

5、 超时等待-TIMED_WAITING

区别于【等待】,它可以在指定的时间自行返回。

6、 终止-TERMINATED

终止】表示线程已经执行完毕。线程正常运行结束或抛出没有捕获的异常或错误进入【终止】状态,已经【终止】的线程不能通过start再次唤醒。

二、Synchronized关键字

可重入锁
分对象锁和类锁
锁升级:偏向锁(记录锁线程ID)-》自旋锁(默认最多自旋10次)-》重量级锁
对象锁的对象需要用final修饰

三、Volatile关键字

作用:保证内存可见性禁止指令重排序,不能保证原子性

3.1、如何保证可见性

从JMM的角度
JMM(Java内存模型)
(笔记三)

  1. JMM下有个天然的happen-before原则:
    volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”是指时间上的先后顺序。
    happens-before:先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。
  2. 从汇编的角度
    有volatile变量修饰的共享变量进行写操作的时候会比没有volatile变量修饰多一行带有Lock前缀的指令,Lock前缀的指令在多核处理器会将当前处理器缓存行的数据写回到系统内存。并且由于CPU缓存一致性协议和总线嗅探机制,这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
    缓存一致性协议:http://t.zoukankan.com/boanxin-p-12106819.html

3.2、如何禁止指令重排序

JMM把内存屏障指令分为:LoadLoad、StoreStore、LoadStore、StoreLoad
在这里插入图片描述

在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。

四、CAS

CAS的全称为Compare-And-Swap/Compare-And-Set,它是一条CPU并发原语。CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。
当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。但可能会产生ABA问题,可以使用版本号来避免。

五、AQS(AbstractQueuedSynchronizer)模板方法模式

AQS内部维护了一个变量volatile int state
同步双向队列由节点Node构成。同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。(Java并表5-5)

AQS提供的模板方法
在这里插入图片描述

六、ReentrantLock

ReentrantLockSynchronized 对比:
Sychronized是非公平锁,JVM层面的锁,JAVA关键字,通过monitor来完成。ReentranLock是API层面的锁,底层使用AQS。
ReetranLock可实现公平锁,实现可中断,超时获取锁。拥有获取锁和释放锁的可操作性,ReetranLock可以绑定条件Condition。
Sychronized锁的是对象,ReetranLock锁的是线程。

七、CountDownLatch 和 CyclicBarrier

CountDownLatchCyclicBarrier都是线程同步的工具类,CountDownLatch允许一个或多个线程一直等待,直到这些线程完成它们的操作。CyclicBarrier是当线程到达某状态后,暂停下来等待其他线程,等到所有线程均到达以后,才继续执行。CountDownLatch调用await()通常是主线程/调用线程,而CyclicBarrier调用await()是在任务线程调用的,CyclicBarrier阻塞的是任务线程,而主线程是不受影响的。这两个类都是基于AQS实现的。当我们在侯建CountDownLatch对象是,传入的值其实就会赋值给AQS的关键变量state,执行countDown()时,会利用CAS将state -1,调用await(),其实就是判断state是否为0,不为0则加入到队列中,将该线程阻塞掉。头节点一直自旋等待state为0,当state为0时,头节点唤醒后面的阻塞队列。CyclicBarrier则是直接借助ReentrantLock加上Condition等待唤醒的功能进而实现的。在构建CyclicBarrier时,传入的值会赋值给CyclicBarrier内部维护的count变量,也会赋值给parties变量(复用的关键)。每次调用await时,将count-1,使用ReentranLock来保证操作count值的线程安全性。如果count不为0,则添加到condition队列中,如果count等于0,则把节点从condition队列添加到AQS的队列中进行全部唤醒,并把parties的值重新赋值给count。

八、ReadWriteLock 读写锁

写锁/排他锁 排斥一切其他锁,包括读锁和写锁
读锁/共享锁 允许其他读锁进入,但不允许写锁进入

九、Semaphore

Semaphore能控制允许多少线程去访问资源,semaphore可以限制并发访问数量,即为限流。

十、LockSupport

LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。
在这里插入图片描述

可以先调用unpark()再调用park()方法。LockSupport是可中断的。
线程中断:https://blog.csdn.net/tianjindong0804/article/details/105134182

十一、Condition

Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
Object的监视器方法与Condition接口的对比
(Java并表5-12)

  1. condition是和ReentrantLock配合使用的,condition要和某一个ReentrantLock绑定.
  2. 当在一个线程调用了condition.await()方法后,会使释放与condition关联的对象锁,此时,该线程会卡死在这个方法处,直到该方法返回,才会从该方法处重新运行,
    在该方法返回之前,此线程必须重新获取该对象锁,该方法返回后,当前线程一定会拥有该对象锁(官方描述).
  3. 实践后发现,调用condition.await()方法后,会释放当前线程拥有的对象锁(即ReentrantLock),无论此线程对该对象上了多少次锁.
  4. 当该方法返回后,此线程会重新恢复到未调用该方法之前所拥有的对象锁的数量.
  5. 一个ReentrantLock可以和多个Condition绑定,多个condition可以分别在不同的子线程里面调用await方法,然后使用对应的condition唤醒某一个因该condition在等待的线程,所以说使用condition可以显示的指定唤醒哪一个线程,而notify却不能.

十二、强软弱虚

  • 强引用就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
  • 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用,主要作用用来管理堆外内存。
    堆外内存:https://blog.csdn.net/qq_27184497/article/details/120905998

十三、ThreadLocal

https://blog.csdn.net/u010445301/article/details/111322569

十四、容器(Collection/Map)

  1. Vector/hashtable 自带锁,不使用
  2. Hashmap
    初始值16 负载因子0.75
    当链表长度大于8,HashMap容量必须大于等于64,链表转红黑树,链表长度小于6,红黑树转链表
    hash值hashCode^(hashCode>>>16)目的是使32位的hashCode高16位与低16位都参与hash值的运算,可以更好的均匀散列,减少碰撞,进一步降低hash冲突的几率
    计算对应数组下标:n是2的次方条件下,(n-1)&hash值?等同于hash%n是一样的?
  3. ConcurrentHashMap
    JDK1.7:reetranlock+segment+hashEntry
    JDK1.8:sychronized+voliate+cas+node
  4. ConcurrentSkipListMap 跳表 类似Redis的跳表
  5. CopyOnWriteArrayList 写时复制数组 读无锁,add(E)或set(int,E)则先复制一个新的数组,将元素复制过去,add(E),复制旧数组长度+1的数组并添加新元素,set(int,E),如果新元素和旧元素相同则直接返回旧数组,如果不同则复制新数组再赋值新元素

十五、BlockingQueue阻塞队列

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。
1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。
(Java并表6-1)

抛出异常:当队列满时,如果再往队列里插入元素,会抛出IllegalStateException(“Queuefull”)异常。当队列空时,从队列里获取元素会抛出NoSuchElementException异常。
返回特殊值:当往队列插入元素时,会返回元素是否插入成功,成功返回true。如果是移除方法,则是从队列里取出一个元素,如果没有则返回null。
一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。
超时退出:当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,如果超过了指定的时间,生产者线程就会退出。

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
    ArrayBlockingQueue是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序访问队列,即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列。为了保证公平性,通常会降低吞吐量。
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
    LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
    PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。
    DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
    DelayQueue非常有用,可以将DelayQueue运用在以下应用场景。
    ·缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
    ·定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现的。
  • SynchronousQueue:一个不存储元素的阻塞队列。
    SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。
    它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。使用以下构造方法可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的顺序访问队列。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
    LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。
    transfer方法如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
    tryTransfer方法是用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回,而transfer方法是必须等到消费者消费了才返回。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
    LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。

十六、Future/CompletableFuture

Future接口有5个方法:

  1. boolean cancel(boolean mayInterruptIfRunning)
    尝试取消当前任务的执行。如果任务已经取消、已经完成或者其他原因不能取消,尝试将失败。如果任务还没有启动就调用了cancel(true),任务将永远不会被执行。如果任务已经启动,参数mayInterruptIfRunning将决定任务是否应该中断执行该任务的线程,以尝试中断该任务。
    如果任务不能被取消,通常是因为它已经正常完成,此时返回false,否则返回true
  2. boolean isCancelled()
    如果任务在正常结束之前被被取消返回true
  3. boolean isDone()
    正常结束、异常或者被取消导致任务完成,将返回true
  4. V get()
    等待任务结束,然后获取结果,如果任务在等待过程中被终端将抛出InterruptedException,如果任务被取消将抛出CancellationException,如果任务中执行过程中发生异常将抛出ExecutionException。
  5. V get(long timeout, TimeUnit unit)
    任务最多在给定时间内完成并返回结果,如果没有在给定时间内完成任务将抛出TimeoutException。

十七、Executor框架

在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。这种两级调度模型的示意图如图所示。(Java并图10-1)
从图中可以看出,应用程序通过Executor框架控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。
Executor框架主要由3大部分组成如下。

  • 任务。包括被执行任务需要实现的接口:Runnable接口或Callable接口。

  • 任务的执行。包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。

  • 异步计算的结果。包括接口Future和实现Future接口的FutureTask类。
    (java并图10-3)

  • 主线程首先要创建实现Runnable或者Callable接口的任务对象。工具类Executors可以把一个Runnable对象封装为一个Callable对象(Executors.callable(Runnable task)或Executors.callable(Runnable task,Objectresule))。

  • 然后可以把Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnablecommand));或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnabletask)或ExecutorService.submit(Callabletask))。

  • 如果执行ExecutorService.submit(…),ExecutorService将返回一个实现Future接口的对象(到目前为止的JDK中,返回的是FutureTask对象)。由于FutureTask实现了Runnable,程序员也可以创建FutureTask,然后直接交给ExecutorService执行。

  • 最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

十八、线程池ThreadPoolExecutor及7个参数

int corePoolSize:核心线程数
int maximumPoolSize:最大线程数
long keepAliveTime 存活时间
TimeUnit unit 存活时间单位
BlockingQueue workQueue 用来暂时保存任务的工作队列。
ThreadFactory threadFactory 线程工厂-》创建线程的工厂
RejectedExecutionHandler handler 拒绝策略-》当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler

十九、Executors

  1. newSingleThreadExecutor
    New ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())newCachedThreadPool
  2. newFixedThreadPool
    New ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue(),threadFactory)
  3. newCachedThreadPool
    new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue())
  4. newScheduledThreadPool
    new ThreadPoolExecutor(corePoolSize,Integer.MAX_VALUE,0,NANOSECONDS,new DelayedWorkQueue())

二十、线程池的大小

Nthreads = NcpuUcpu(1+W/C)
Ncpu是处理器的核的数目
Ucpu是期望的CPU利用率
W/C是等待时间与计算时间的比率
CPU密集型配置CPU数+1的线程池,IO密集型配置2*N的线程池,优先级不同的任务使用优先队列,先后执行的也用优先队列,建议使用有界队列

二十一、ForkJoinPool

任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
原理:多个work queue/采用 work stealing算法
ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制。通常情况下,我们不需要直接继承ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了以下两个子类。
·RecursiveAction:用于没有返回结果的任务。
·RecursiveTask:用于有返回结果的任务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值