赞
踩
简单来说就是:
出现线程安全问题的原因一般都是三个原因:
线程切换带来的原子性问题 解决办法:使用多线程之间同步synchronized或使用锁(lock)。
缓存导致的可见性问题 解决办法:synchronized、volatile、LOCK,可以解决可见性问题
编译优化带来的有序性问题 解决办法:Happens-Before 规则可以解决有序性问题
做一个形象的比喻:
一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
进程与线程的区别
1、 避免一个线程同时获得多个锁;
2、 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源;
3、 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制;
- public class MyThread extends Thread {
-
- @Override
- public void run() {
-
- System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
- }
-
- public class MyRunnable implements Runnable {
-
- @Override
- public void run() {
-
- System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
- }
-
- public class MyCallable implements Callable<Integer> {
-
- @Override
- public Integer call() {
-
- System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
- return 1;
- }
- }
-
- public class CreateRunnable {
-
- public static void main(String[] args) {
-
- //创建多线程创建开始
- Thread thread = new Thread(new Runnable() {
-
- public void run() {
-
- for (int i = 0; i < 10; i++) {
-
- System.out.println("i:" + i);
- }
- }
- });
- thread.start();
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
相同点:
这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
总结:调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
新建(new):新创建了一个线程对象。
就绪(可运行状态)(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
阻塞的情况分三种:
(一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waittingqueue)中,使本线程进入到等待阻塞状态;
(二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
(三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
死亡(dead)(结束):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。(Java是由JVM中的线程计数器来实现线程调度)
有两种调度模型:分时调度模型和抢占式调度模型。
分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU的时间片这个也比较好理解。
Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:(1)线程体中调用了 yield 方法让出了对 cpu 的占用权利(2)线程体中调用了 sleep 方法使线程进入睡眠状态(3)线程由于 IO 操作受到阻塞(4)另外一个更高优先级线程出现(5)在支持时间片的系统中,该线程的时间片用完
(1)wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException 异常;(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
两者都可以暂停线程的执行
- synchronized (monitor) {
-
- // 判断条件谓词是否得到满足
- while(!locked) {
-
- // 等待唤醒
- monitor.wait();
- }
- // 处理其他的业务逻辑
- }
-
(1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;(2)线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
在java中有以下3种方法可以终止正在运行的线程:
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
使用interrupt方法中断线程。
可以通过中断 和 共享变量的方式实现线程间的通讯和协作
比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。
Java中线程通信协作的最常见方式:
一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
线程间直接的数据交换:
三.通过管道进行线程间通信:字节流、字符流
请知道一条原则:同步的范围越小越好。
当一个线程对共享的数据进行操作时,应使之成为一个”原子操作“,即在没有完成相关操作之前,不允许其他线程打断它,否则,就会破坏数据的完整性,必然会得到错误的处理结果,这就是线程的同步。
在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间同时等待对方释放资源的时候就会形成线程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。
实现线程同步的方法
同步代码方法:sychronized 关键字修饰的方法
同步代码块:sychronized 关键字修饰的代码块
使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
使用重入锁实现线程同步:reentrantlock类是可冲入、互斥、实现了lock接口的锁他与sychronized方法具有相同的基本行为和语义
- Lock lock = new ReentrantLock();
- lock. lock();
- try {
-
- System. out. println("获得锁");
- } catch (Exception e) {
-
- // TODO: handle exception
- } finally {
-
- System. out. println("释放锁");
- lock. unlock();
- }
-
方法名 | 描述 |
---|---|
sleep() | 强迫一个线程睡眠N毫秒 |
isAlive() | 判断一个线程是否存活。 |
join() | 等待线程终止。 |
activeCount() | 程序中活跃的线程数。 |
enumerate() | 枚举程序中的线程。 |
currentThread() | 得到当前线程。 |
isDaemon() | 一个线程是否为守护线程。 |
setDaemon() | 设置一个线程为守护线程。 |
setName() | 为线程设置一个名称。 |
wait() | 强迫一个线程等待。 |
notify() | 通知一个线程继续运行。 |
setPriority() | 设置一个线程的优先级。 |
1、 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去;
2、 然后,线程B到主内存中去读取线程A之前已更新过的共享变量;
下面通过示意图来说明线程之间的通信
- int a = 5; //语句1
- int r = 3; //语句2
- a = a + 2; //语句3
- r = a*a; //语句4
-
1、 编译器优化的重排序编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
2、 指令级并行的重排序现代处理器采用了指令级并行技术(ILP)来将多条指令重叠执行如果不;存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3、 内存系统的重排序由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在;乱序执行。
1、 不管怎么排序,结果不能改变;
2、 不存在数据依赖的可以被编译器和处理器重排序;
3、 一个操作依赖两个操作,这两个操作如果不存在依赖可以重排序;
4、 单线程根据此规则不会有问题,但是重排序后多线程会有问题;
synchronized关键字最主要的三种使用方式:
总结:synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(Stringa)因为JVM中,字符串常量池具有缓存功能!
**双重校验锁实现对象单例(线程安全)说明:**双锁机制的出现是为了解决前面同步问题和性能问题,看下面的代码,简单分析下确实是解决了多线程并行进来不会出现重复new对象,而且也实现了懒加载
- public class Singleton {
-
- private volatile static Singleton uniqueInstance;
- private Singleton() {
-
- }
- public static Singleton getUniqueInstance() {
-
- //先判断对象是否已经实例过,没有实例化过才进入加锁代码
- if (uniqueInstance == null) {
-
- //类对象加锁
- synchronized (Singleton.class) {
-
- if (uniqueInstance == null) {
-
- uniqueInstance = new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
1、 为uniqueInstance分配内存空间;
2、 初始化uniqueInstance;
3、 将uniqueInstance指向分配的内存地址;
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时uniqueInstance 还未被初始化。使用volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
1、 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为;monitor的所有者。
2、 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.;
3、 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再;重新尝试获取monitor的所有权。
synchronized是可以通过 反汇编指令 javap命令,查看相应的字节码文件。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
(1)volatile 修饰变量(2)synchronized 修饰修改变量的方法(3)wait/notify(4)while 轮询
(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。(3)CAS 是基于冲突检测的乐观锁(非阻塞)
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
所以从Oracle Java Spec里面可以看到:
区别
不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger 和 BigDecimal 等。
只有满足如下状态,一个对象才是不可变的;
它的状态不能在创建后再被修改;
所有域都是 final 类型;并且,它被正确创建(创建期间没有发生 this 引用的逸出)。
不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。
java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的(AtomicInteger,AtomicBoolean,AtomicLong)
简单来说就是原子类来实现CAS无锁模式的算法
死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,这就是所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
Java 中导致饥饿的原因:
1、高优先级线程吞噬所有的低优先级线程的 CPU 时间。
2、线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
3、线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来许多好处。
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用
构造参数图
构造参数参数介绍:
corePoolSize 核心线程数量>maximumPoolSize 最大线程数量>keepAliveTime 线程保持时间,N个时间单位>unit 时间单位(比如秒,分)>workQueue 阻塞队列>threadFactory 线程工厂>handler 线程池拒绝策略
Executor工厂类如何创建线程池图:
Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:
1、 newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;
2、 newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;
3、 newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行;
4、 newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行;
1. newCachedThreadPool
- package com.demo;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class TestNewCachedThreadPool {
-
- public static void main(String[] args) {
-
- // 创建无限大小线程池,由jvm自动回收
- ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
- for (int i = 0; i < 10; i++) {
-
- final int temp = i;
- newCachedThreadPool.execute(new Runnable() {
-
- public void run() {
-
- try {
-
- Thread.sleep(100);
- } catch (Exception e) {
-
- }
- System.out.println(Thread.currentThread().getName() +",i==" + temp);
- }
- });
- }
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
2.newFixedThreadPool
Runtime.getRuntime().availableProcessors()方法是查看电脑CPU核心数量)
- package com.demo;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class TestNewFixedThreadPool {
-
- public static void main(String[] args) {
-
- ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
- for (int i = 0; i < 10; i++) {
-
- final int temp = i;
- newFixedThreadPool.execute(new Runnable() {
-
- public void run() {
-
- System.out.println(Thread.currentThread().getName() + ",i==" + temp);
- }
- });
- }
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
3.newScheduledThreadPool
- package com.demo;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ScheduledExecutorService;
- import java.util.concurrent.TimeUnit;
- public class TestNewScheduledThreadPool {
-
- public static void main(String[] args) {
-
- //定义线程池大小为3
- ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
- for (int i = 0; i < 10; i++) {
-
- final int temp = i;
- newScheduledThreadPool.schedule(new Runnable() {
-
- public void run() {
-
- System.out.println("i:" + temp);
- }
- }, 3, TimeUnit.SECONDS);//这里表示延迟3秒执行。
- }
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
4.newSingleThreadExecutor
- package com.demo;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class TestNewSingleThreadExecutor {
-
- public static void main(String[] args) {
-
- ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
- for (int i = 0; i < 10; i++) {
-
- final int index = i;
- newSingleThreadExecutor.execute(new Runnable() {
-
- public void run() {
-
- System.out.println(Thread.currentThread().getName() + "index:" + index);
- try {
-
- Thread.sleep(200);
- } catch (Exception e) {
-
- }
- }
- });
- }
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
相同点:相同点就是都可以开启线程执行池中的任务。
不同点:
接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和Callable 类型的任务。
返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
异常处理:submit()方便Exception处理
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时ThreadPoolTaskExecutor 定义一些策略:
corePoolSize 核心线程数量>maximumPoolSize 最大线程数量>keepAliveTime 线程保持时间,N个时间单位>unit 时间单位(比如秒,分)>workQueue 阻塞队列>threadFactory 线程工厂>handler 线程池拒绝策略
- package com.demo;
- import java.util.concurrent.ArrayBlockingQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- public class Test001 {
-
- public static void main(String[] args) {
-
- //创建线程池
- ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
- for (int i = 1; i <= 6; i++) {
-
- TaskThred t1 = new TaskThred("任务" + i);
- //executor.execute(t1);是执行线程方法
- executor.execute(t1);
- }
- //executor.shutdown()不再接受新的任务,并且等待之前提交的任务都执行完再关闭,阻塞队列中的任务不会再执行。
- executor.shutdown();
- }
- }
- class TaskThred implements Runnable {
-
- private String taskName;
- public TaskThred(String taskName) {
-
- this.taskName = taskName;
- }
- public void run() {
-
- System.out.println(Thread.currentThread().getName() + taskName);
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
1、 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没;有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队;列里。如果工作队列满了,则进入下个流程。
3、 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任;务。如果已经满了,则交给饱和策略来处理这个任务。
什么是IO密集
分配CPU和IO密集:
1、 CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在;执行任务
2、 IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数;
精确来说的话的话:
从以下几个角度分析任务的特性:
任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
任务的优先级:高、中、低。
任务的执行时间:长、中、短。
任务的依赖性:是否依赖其他系统资源,如数据库连接等。
可以得出一个结论:
1、 ArrayList添加方法源码;
2、 Vector添加源码(加锁了synchronized关键字);
因为HasTable的内部方法都被synchronized修饰了,所以是线程安全的。其他的都和HashMap一样
1、 HashMap添加方法的源码;
2、 HashTable添加方法的源码;
1、 HashTable就是实现了HashMap加上了synchronized,而ConcurrentHashMap底层采用分;段的数组+链表实现,线程安全
2、 ConcurrentHashMap通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效;率提升N倍,默认提升16倍。
3、 并且读操作不加锁,由于HashEntry的value变量是volatile的,也能保证读取到最新的值;
4、 Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,;ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
5、 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进;行扩容),插入前检测需不需要扩容,有效避免无效扩容
注意:* 号代表后面是还有内容的
那就有可能要说了,我们并发集合不是也可以实现多线程之间的数据共享吗,其实也是有区别的:
非堵塞队列:;
ArrayDeque,(数组双端队列);ArrayDeque (非堵塞队列)是JDK容器中的一个双端队列实现,内部使用数组进行元素存储,不允许存储null值,可以高效的进行元素查找和尾部插入取出,是用作队列、双端队列、栈的绝佳选择,性能比LinkedList还要好。2、 PriorityQueue,(优先级队列);PriorityQueue (非堵塞队列) 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象3、 ConcurrentLinkedQueue,(基于链表的并发队列);ConcurrentLinkedQueue (非堵塞队列): 是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。ConcurrentLinkedQueue的性能要好于BlockingQueue接口,它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。该队列不允许null元素。
堵塞队列:;**1.DelayQueue, (基于时间优先级的队列,延期阻塞队列)**DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。**2.ArrayBlockingQueue, (基于数组的并发阻塞队列)ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据3.LinkedBlockingQueue, (基于链表的FIFO阻塞队列)**LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。**4.LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列)**LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比于其他阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法,以first结尾的方法,表示插入、获取获移除双端队列的第一个元素。以last结尾的方法,表示插入、获取获移除双端队列的最后一个元素。LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。**5.PriorityBlockingQueue, (带优先级的无界阻塞队列)**priorityBlockingQueue是一个无界队列,它没有限制,在内存允许的情况下可以无限添加元素;它又是具有优先级的队列,是通过构造函数传入的对象来判断,传入的对象必须实现comparable接口。**6. SynchronousQueue (并发同步阻塞队列)**SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。将这个类称为队列有点夸大其词。这更像是一个点。
并发队列的常用方法不管是那种列队,是那个类,当是他们使用的方法都是差不多的
方法名 | 描述 |
---|---|
add() | 在不超出队列长度的情况下插入元素,可以立即执行,成功返回true, |
如果队列满了就抛出异常。 | |
offer() | 在不超出队列长度的情况下插入元素的时候则可以立即在队列的尾部插入指定元素,成功时返回true,如果此队列已满,则返回false。 |
put() | 插入元素的时候,如果队列满了就进行等待,直到队列可用。 |
take() | 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。 |
poll(long timeout,TimeUnit unit) | 在给定的时间里,从队列中获取值,如果没有取到会抛出异常。 |
remainingCapacity() | 获取队列中剩余的空间。 |
remove(Object o) | 从队列中移除指定的值。 |
contains(Object o) | 判断队列中是否拥有该值。 |
drainTo(Collectionc) | 将队列中值,全部移除,并发设置到给定的集合中。 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。