Java 多线程、Queue学习,CAS学习
主题一: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类所具有的优势:
- 适合多个相同的程序代码的线程去处理同一个资源
- 可以避免java中的单继承的限制
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
【Java并发编程】之六: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(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
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得到。
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 关键字原理
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)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)
自旋锁与互斥锁:
- 自旋锁与互斥锁都是为了实现保护资源共享的机制。
- 无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
- 获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。
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其实和锁有点类似,它一般用于控制对某组资源的访问权限。