美文网首页
Java 多线程、Queue学习,CAS学习

Java 多线程、Queue学习,CAS学习

作者: John13 | 来源:发表于2018-09-17 22:38 被阅读0次

主题一:Queue:

Java并发(10)- 简单聊聊JDK中的七大阻塞队列
解读 Java 并发队列 BlockingQueue
Java多线程总结之线程安全队列Queue
并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法
Java多线程总结之聊一聊Queue ---- 棒棒的!
Core Java 并发:理解并发概念

  • 并行和并发区别:

1、并行是指两者同时执行一件事,比如赛跑,两个人都在不停的往前跑;
2、并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率

在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列(先进先出)。
Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列。

阻塞队列(同步队列)--典型BlockingQueue: -- 线程安全

  • 1. ArrayBlockQueue:
    一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象必须明确大小,像数组一样。

  • 2、LinkedBlockingQueue:
    由于LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
    LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。

  • 3. PriorityBlockingQueue:
    类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。

  • 4. SynchronousQueue:
    同步队列。同步队列没有任何容量,每个插入必须等待另一个线程移除,反之亦然。

非阻塞队列(并发队列)--典型ConcurrentLinkedQueue: -- 线程安全

  • 1、ConcurrentLinkedQueue:
    ConcurrentLinkedQueue,它是一个无锁的并发线程安全的队列,是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。

主题二:线程:

实现Runnable接口比继承Thread类所具有的优势:
线程里存在两个队列:
  • 阻塞队列:wait、sleep、join
  • 就绪队列:notify唤醒、wait时间过了、
线程的不安全体现:
  • 内存可见性
  • 操作原子性

Java并发编程:Thread类的使用
Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比
一份针对于新手的多线程实践
Java并发编程:同步容器
深入理解线程通信
java自带线程池和队列详细讲解

interrupt(线程中断):

调用Thread的interrupt结束线程:

其实调用Thread对象的interrupt函数并不是立即中断线程,只是将线程中断状态标志设置为true,当线程运行中有调用其阻塞的函数(Thread.sleep,Object.wait,Thread.join等)时,阻塞函数调用之后,会不断地轮询检测中断状态标志是否为true,如果为true,则停止阻塞并抛出InterruptedException异常,同时还会重置中断状态标志;如果为false,则继续阻塞,直到阻塞正常结束。

interrupt()方法有两个作用:

  • 一个是将线程的中断状态置位(中断状态由false变成true);
  • 另一个则是让被中断的线程抛出InterruptedException异常。

这是很重要的。这样,对于那些阻塞方法(比如 wait() 和 sleep())而言,当另一个线程调用interrupt()中断该线程时,该线程会从阻塞状态退出并且抛出中断异常。这样,我们就可以捕捉到中断异常,并根据实际情况对该线程从阻塞方法中异常退出而进行一些处理。

比如说:线程A获得了锁进入了同步代码块中,但由于条件不足调用 wait() 方法阻塞了。这个时候,线程B执行 threadA.interrupt()请求中断线程A,此时线程A就会抛出InterruptedException,我们就可以在catch中捕获到这个异常并进行相应处理(比如进一步往上抛出)

关于Thread的静态函数interrupted与Thread的对象函数isInterrupted:

  • 2函数都是调用了Native函数private native boolean isInterrupted(boolean ClearInterrupted);
  • 前者调用传的参数为true,所以,调用interrupted函数,会在检测线程中断状态标志是否为true后,还会将中断状态标志重置为false。
  • 而isInterrupted函数只是检测线程中断状态标志。

如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。 我们可以捕获该异常,并且做一些处理。
另外,Thread.interrupted()方法是一个静态方法,它是判断当前线程的中断状态,需要注意的是,线程的中断状态会由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

JAVA多线程之中断机制(如何处理中断?)

Sleep(线程睡眠):

Wait:

Join(线程合并):

线程合并是优先执行调用该方法的线程,再执行当前线程
所谓合并,就是等待其它线程执行完,再执行当前线程,执行起来的效果就好像把其它线程合并到当前线程执行一样。

Yield(线程让步):

线程让步用于正在执行的线程,在某些情况下让出CPU资源,让给其它线程执行,来看一个小例子:


主题三:多线程:

Future、Callable、FutureTask:

Java并发编程:Callable、Future和FutureTask

  • Callable和Future,它俩很有意思的,一个产生结果,一个拿到结果。
  • Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,
  • 而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值
  • FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到。

Java进阶之FutureTask的用法及解析

Synchronized:

既保证了多线程的并发有序性,又保证了多线程的内存可见性

  • synchronized底层的原理是跟jvm指令和monitor有关系的。

  • synchronized一般是对对象加锁,对类加锁也就是对类对象加锁。如果使用了synchronized关键字,在底层编译后的jvm指令中,会有monitorenter和monitorexit两个指令。线程进入synchronized代码片段,执行monitorenter指令对monitor计数器加1,这样其他线程发现monitor的计数器不为0,就阻塞等待:


  • 线程出synchronized代码片段,执行monitorexit指令就是对monitor计数器减1,这样其他线程发现monitor的计数器为0,就可以拿到锁,给monitor的计数器加1,然后执行业务逻辑了:


  • 上面的是针对synchronized对对象、类加锁的底层原理。方法加锁不是通过monitor指令,而是通过ACC_SYNCHORNIZED关键字,判断方法是否同步。

Synchronized和Lock比较:

synchronized四种锁状态的升级

  • 锁可以升级但不能降级:无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态
  • 偏向锁通过对比 Mark Word 解决加锁问题,避免执行CAS操作。
  • 轻量级锁是通过用 CAS 操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。
  • 重量级锁是将除了拥有锁的线程以外的线程都阻塞。

synchronized四种锁状态的升级

synchronized 关键字原理
Java并发编程:synchronized

atomic(原子类包):

java.util.concurrent.atomic,该包是对Java部分数据类型的原子封装,在原有> 数据类型的基础上,提供了原子性的操作方法,保证了线程安全
虽然使用CAS可以实现非阻塞式的原子性操作,但是会产生ABA问题

  • Compare and Swap, 翻译成比较并交换。
  • CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
  • java应用:
    java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。
    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包。可见CAS的重要性。
  • ABA问题解决:
    JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。

自旋锁存在的问题:

  • 如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
  • 上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

自旋锁的优点:

  • 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
  • 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

自旋锁与互斥锁:

  • 自旋锁与互斥锁都是为了实现保护资源共享的机制。
  • 无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
  • 获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。

CAS原理分析
java的atomic包使用

Volatile:

  • volatile可以保证内存可见性,不能保证并发有序性,既保证不了执行的原子性
  • 防止指令重排

你应该知道的 volatile 关键字
Java并发编程:volatile关键字解析

相比于synchroinized来说,volatile要轻量很多,执行的成本会更低。原因是volatile不会引起线程上下文的切换和调度,但是它与synchronized的意义其实是有区别的。synchronized关键字主要体现的是互斥性。

ReentrantLock、Condition:

  • 线程互斥: 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题。
  • 线程通信:
  • Condition将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
  • Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
  • Condition的强大之处在于它可以为多个线程间建立不同的Condition

ReentrantLock 实现原理
Java并发编程:Lock
Java线程(篇外篇):线程和锁

  • AbstractQueuedSynchronized(AQS):抽象的队列式的同步器

ThreadLocal:

Java并发编程:深入剖析ThreadLocal
ThreadLocal就是这么简单

ThreadLocal内存泄漏的根源是:

  • 由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

  • 想要避免内存泄露就要手动remove()掉!

线程池:

  • FixedThreadPool:
    创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。
    在FixedThreadPool中,有一个固定大小的池,如果当前需要执行的任务超过了池大小,那么多于的任务等待状态,直到有空闲下来的线程执行任务,而当执行的任务小于池大小,空闲的线程也不会去销毁。
    ExecutorService threadPool = Executors.newFixedThreadPool(3);// 创建可以容纳3个线程的线程池
  • CachedThreadPool:
    创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
    CachedThreadPool会创建一个缓存区,将初始化的线程缓存起来,如果线程有可用的,就使用之前创建好的线程,如果没有可用的,就新创建线程,终止并且从缓存中移除已有60秒未被使用的线程。
    ExecutorService threadPool = Executors.newCachedThreadPool();// 线程池的大小会根据执行的任务数动态分配
  • SingleThreadExecutor:
    创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
    SingleThreadExecutor得到的是一个单个的线程,这个线程会保证你的任务执行完成,如果当前线程意外终止,会创建一个新线程继续执行任务,这和我们直接创建线程不同,也和newFixedThreadPool(1)不同。
    ExecutorService threadPool = Executors.newSingleThreadExecutor();// 创建单个线程的线程池,如果当前线程在执行任务时突然中断,则会创建一个新的线程替代它继续执行任务
  • ScheduledThreadPool:
    创建一个可安排在给定延迟后运行命令或者定期地执行的线程池。
    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);// 效果类似于Timer定时器

CountDownLatch、CyclicBarrier和Semaphore

Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

1、CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
  1)、CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
  2)、而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时往下执行;
  另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2、Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

相关文章

网友评论

      本文标题:Java 多线程、Queue学习,CAS学习

      本文链接:https://www.haomeiwen.com/subject/btljnftx.html