Java多线程导论及延申

我的博客中有更多后端开发面试题,点我查看!

Java并发模块框架

并发大全

2万字参透并发编程

  • jvm中线程分为哪些状态
  • 在执行Thread.start()方法后,线程是不是马上运行。

多线程导论

  • 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
  • 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  • 有序性即程序执行的顺序按照代码的先后顺序执行。

多线程设计模式解读—Producer-Consumer模式

Thread statue

请简述一下线程的sleep()方法和yield()方法有什么区别?

  • sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会
  • 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态
  • sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常

在Java中wait和seelp方法的不同

  • wait 作用于对象,sleep作用于 Thread
  • wait 释放对象锁,sleep 不释放对象锁
  • wait 运行于同步方法或同步代码块,sleep 可运行于任何地方
  • wait 不需要捕获异常,sleep 需要捕获异常

Thread类中的yield方法有什么作用?

暂停当前线程,将当前线程从 Running状态转到 Ready状态,让出控制权给其他线程

谈谈 wait / notify 关键字的理解

  • wait / notify 存在于所有对象;
    使用时需要 synchronized,否则 IllegalMonitorStateException
  • 调用 wait方法会让当前线程阻塞,让出对象锁;若wait有设置时间参数,到时间后自动唤醒;
  • notify一次唤醒一个等待的线程;notifyAll一次性唤醒所有该对象上的等待线程。

为什么wait, notify 和 notifyAll这些方法不在thread类里面?

wait,notify,notifyAll,sleep这些方法都跟线程的状态变化有关,为什么jdk把前三个方法放在Object类里面,而把sleep放在Thread类里面?

  • Java 内置锁机制中,锁是对象级而不是线程级,任何对象都能创建锁;
  • 一个线程可以有多个锁,若跟线程绑定可能会不够用。

进程和线程之间的通信方式

  • 进程:无名管道、有名管道、信号、共享内存、消息队列、信号量

  • 线程:互斥量、读写锁、自旋锁、线程信号、条件变量

创建线程的方法

  • 继承Thread类创建线程
  • 实现Runnable接口创建线程
  • 使用CallableFuture创建线程
  • 使用线程池例如用Executor框架

如何停止一个线程

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  • 使用interrupt方法中断线程。
  • 不推荐使用 stop、suspend及resume 方法。相当于电脑断电关机一样,是不安全的方法。

线程同步的方法。

同步的实现方面有两种,分别是synchronized,waitnotify

  • wait():使一个线程处于等待状态,并且释放所持有的对象的lock。sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
  • notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
  • notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

原子操作的实现原理是通过CAS实现的

一般问CAS底层原理,我遇到这种问题,直接说比较并更新的过程,底层实现调用了unsafe类,unsafe类是用C++实现的,没研究过,代码。糊弄过去的

countDownLatch

countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrierSemaphoreconcurrentHashMapBlockingQueue

  • countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
CountDownLatch和CyclicBarrier区别:
  • countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
  • CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用

Runnable和Thread区别

1.RunnableThread相比优点有:

  • 由于Java不允许多继承,因此实现了Runnable接口可以再继承其他类,但是Thread明显不可以
  • Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread并不是不可以,而是相比于Runnable来说,不太适合,具体原因文章中有。

当以Thread方式去实现资源共享时,实际上源码内部是将Thread向下转型为了Runnable,实际上内部依然是以Runnable形式去实现的资源共享

2.Runnable为什么不可以直接run
阐述文章中已有,Runnable其实相对于一个Task,并不具有线程的概念,如果你直接去调用Runnablerun,其实就是相当于直接在主线程中执行了一个函数而已,并未开启线程去执行,带来的“灾害”文章中有。

Java线程池中submit() 和 execute()方法有什么区别?

submit()可以获取返回值,execute()不行。

RUN()Start()方法区别是,Start()会重新创建一个线程去运行,而RUN()是只是去单独的调用。

可以用RUN()完成线程的同步

CompletableFuture,这个是JDK1.8里的新特性,通过它怎么实现多线程并发控制?

多线程实操

面试题,子线程10次子线程2执行20次与主线程100次来回循环执行50次

如何让多个线程交替执行?

一种是基于synchronizedwait/notify,另外一种是基于LockCondition(ReentrantLock).

  • 循环内加锁
  • 判断共享状态变量,如果状态值表示还没轮到当前线程执行,则调用调用锁对象的wait方法
  • 等待状态变化的线程被唤醒,执行任务,然后改变状态,然后调用锁对象的notify方法

用户自己定义一个类,用这个类继承thread类,实现threadrun方法,然后再使用用户自己定义这个类创建一个对象,最后调用thread类的start方法激活线程。

VOLATILE

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。

1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量强制刷新到主内存中去;

2.这个写操作会导致其他线程中的缓存无效。

  • 禁止指令重排序优化
  • 不能保证原子性

volatile对于单个的共享变量的读/写具有原子性,但是像num++这种复合操作,volatile无法保证其原子性,当然文中也提出了解决方案,就是使用并发包中的原子操作类,通过循环CAS地方式来保证num++操作的原子性

volatile 只保证可见性 有序性,不保证原子性的原因,在于寄存器无法被volatile刷新

  • 你说的本地缓存,指的是线程工作内存,不是cpu的寄存器
  • 线程工作内存,确实被设置为无效,下次直接从内存里面取,但是寄存器在之前已经进去了,这是导致volatile不能保证原子性的原因。如果volatile也能把寄存器的值给设置为无效,那么原子性就保证了

LOCK前缀指令篇(一):Java 之深入浅出解读 volatile

原创|《菜鸟读并发》java内存模型之volatile深入解读

一文带你理解Java中Lock的实现原理

Synchronized

volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

  • 既可以修饰方法也可以修饰代码块

对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。 对于同步代码块。JVM采用monitorentermonitorexit两个指令来实现同步。

  • 同步代码块使用monitorentermonitorexit两个指令实现。可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。

Synchronize关键字原理

在这里插入图片描述

sychronized是java中最基本同步互斥的手段,可以修饰代码块,方法,类.
在修饰代码块的时候需要一个reference对象作为锁的对象.
在修饰方法的时候默认是当前对象作为锁的对象.
在修饰类时候默认是当前类的Class对象作为锁的对象.

synchronized会在进入同步块的前后分别形成monitorentermonitorexit字节码指令.在执行monitorenter指令时会尝试获取对象的锁,如果此没对象没有被锁,或者此对象已经被当前线程锁住,那么锁的计数器加一,每当monitorexit被锁的对象的计数器减一.直到为0就释放该对象的锁.由此synchronized是可重入的,不会出现自己把自己锁死.

sychronized是java中最基本同步互斥的手段,可以修饰代码块,方法,类.
在修饰代码块的时候需要一个reference对象作为锁的对象.
在修饰方法的时候默认是当前对象作为锁的对象.
在修饰类时候默认是当前类的Class对象作为锁的对象.

synchronized会在进入同步块的前后分别形成monitorentermonitorexit字节码指令.在执行monitorenter指令时会尝试获取对象的锁,如果此没对象没有被锁,或者此对象已经被当前线程锁住,那么锁的计数器加一,每当monitorexit被锁的对象的计数器减一.直到为0就释放该对象的锁.由此synchronized是可重入的,不会出现自己把自己锁死.

synchronized 和 ReentrantLock有什么区别

除了synchronized的功能,多了三个高级功能.
等待可中断,公平锁,绑定多个Condition.

  • 等待可中断:在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待. tryLock(long timeout, TimeUnit unit)
  • 公平锁:按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁. new RenentrantLock(boolean fair)
  • 绑定多个Condition
    通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal();

JAVA两个字符串如何比较大小

compareTo() 的返回值是int, 它是先比较对应字符的大小(ASCII码顺序)

Lock与synchronized有以下区别:

  • synchronized会自动释放锁,而Lock必须手动释放锁。
  • Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
  • 通过Lock可以知道线程有没有拿到锁,而synchronized不能。
  • Lock能提高多个线程读操作的效率。
  • synchronized能锁住类、方法和代码块,而Lock是块范围内的。

volatile和synchronized的区别

  • volatilesynchronized执行成本更低,因为它不会引起线程上下文的切换和调度
  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile只能用来修饰变量,而synchronized可以用来修饰变量、方法、和类。
  • volatile可以实现变量的可见性,禁止重排序和单次读/写的原子性;而synchronized则可以变量的可见性,禁止重排序和原子性。
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

深入解读synchronized和ReentrantLock

  • synchronized阻塞的线程状态为BLOCKED
  • ReentrantLock阻塞的线程状态为BLOCKED或者WAITTING

深入解读synchronized和ReentrantLock

synchronized 和 ReentrantLock有什么区别

除了synchronized的功能,多了三个高级功能.
等待可中断,公平锁,绑定多个Condition.

  • 等待可中断:在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待. tryLock(long timeout, TimeUnit unit)
  • 公平锁:按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁. new RenentrantLock(boolean fair)
  • 绑定多个Condition
    通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal();

锁升级

synchronized锁升级原理:在锁对象的对象头里面有一个 threadid字段,在第一次访问的时候 threadid为空,jvm让其持有偏向锁,并将threadid设置为其线程id,再次进入的时候会先判断threadid是否与其线程id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6之后优化 synchronized的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

偏向锁

把MARKWORD的threadID改成自己的threadID
在锁竞争不强烈的情况下,通常一个线程会多次获取同一个锁,为了减少获取锁的代价 引入了偏向锁,会在Java对象头中记录获取锁的线程的threadID

  • 当线程发现对象头的threadID存在时。判断与当前线程是否是同一线程。
  • 如果是则不需要再次加、解锁。
  • 如果不是,则判断threadID是否存活。不存活:设置为无锁状态,其他线程竞争设置偏向锁。存活:查找threadID堆栈信息判断是否需要继续持有锁。需要持有则升级threadID线程的锁为轻量级锁。不需要持有则撤销锁,设置为无锁状态等待其它线程竞争。

不是同一个对象就升级为轻量级锁

因为偏向锁的撤销操作还是比较重的,导致进入安全点,因此在竞争比较激烈时,会影响性能,可以使用-XX:-UseBiasedLocking=false禁用偏向锁。

轻量级锁

当偏向锁升级为轻量级锁时,其它线程尝试通过CAS方式设置对象头来获取锁。
给MARKWORD一个指针,指向自己的lock record

  • 会先在当前线程的栈帧中设置Lock Record,用于存储当前对象头中的mark word的拷贝。
  • 复制mark word的内容到lock record,并尝试使用casmark word的指针指向lock record
  • 如果替换成功,则获取偏向锁
  • 替换不成功,则会自旋重试一定次数。
  • 自旋一定次数或有新的线程来竞争锁时,轻量级锁膨胀为重量级锁。
重量级锁

自旋是消耗CPU的,因此在自旋一段时间,或者一个线程在自旋时,又有新的线程来竞争锁,则轻量级锁会膨胀为重量级锁。重量级锁,通过monitor实现,monitor底层实际是依赖操作系统的mutex lock(互斥锁)实现。需要从用户态,切换为内核态,成本比较高

CAS

CAS可能遇到ABA问题,即内存中的值为A,变为B后,又变为了A,此时A为新值,不应该替换。可以采取:A-1B-2A-3的方式来避免这个问题

ABA问题是一种异常现象:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令时就可能出现这个问题(如果在没有垃圾回收机制的环境 中)。在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作。在大多数情况下,这种判断是足够的。然而,有时候还需要知道 “自从上次看到V的值为A以来,这个值是否发生了变化?”在某些算法中,如果V值首先由A编程B,在由B编程A,那么仍然被认为发生了变化,并需要重新执 行算法中的某些步骤。

如果在算法中采用自己的方式来管理节点对象的内存,那么可能出现ABA问题。在这种情况下,即使链表的头结点仍然只想之前观察到的节点,那么也不足 以说明链表的内容没有发生变化。

如果通过垃圾回收器来管理链表节点仍然无法避免ABA问题,那么还有一个相对简单的解决方法:不是只是更新某个引用的值, 而是更新两个值,包含一个引用和一个版本号。即使这个值由A变成B,然后又变为A,版本号也将是不同的。

AtomicStampedReference以 及AtomicMarkableReference支持在两个变量上执行原子的条件更新。AtomicStampedReference将更新一个“对象 —-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题。类似地,AtomicMarkableReference将更新一个“对象引用—- 布尔值”二元组,在某些算法中将通过这种二元组使节点保存在链表中同时又将其标记为“已删除节点”。

参考

ReentrantLock

以对象的方式来操作对象锁.相对于sychronized需要在finally中去释放锁,需要手动释放。

ReentrantLock里面的truefalse 就是 公平锁和非公平锁的实现

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。

ReentrantLockstate字段表示当前线程重入锁的次数,当state为0时候,表示锁是空闲的

compareAndSetState(0, 1)表示当state0,直接更新为1,即空闲时候,直接CAS上锁;线程如果更新成功了,setExclusiveOwnerThread()设置当前线程为锁的占有

可重入锁原理

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;

当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁

ReentrantLock

还是使用上面的例子,ReentrantLock的使用很简单,使用new ReentrantLock(boolean isFair)来创建一个公平或者非公平锁,使用.lock()方法加锁。使用.unlock()方法,因此我们就从这三个方法入手,来简单的看一下它是如何实现锁的。

ReentrantLock主要是用了一个巧妙的数据结构(带头尾指针的双链表)和CAS加自旋以及使用LockSupportpark,unpark(类似wait,notify)来实现加锁和解锁。

CAS原理

AQS (AbstractQueuedSynchronizer)
由一个state和cas 阻塞队列组成

什么是AQS

AQS(AbstractQueuedSynchronizer)AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer的内部类 Sync。可见 CountDownLatch 是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。(eg. Semaphore )

AQS用法

如上所述,AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock用它来表现拥有它的线程已经请求了多少次锁;FutureTask用它来表现任务的状态(尚未开始、运行、完成和取消)

AQS AbstractQueuedSynchronizer

J.U.C是基于AQS实现的,AQS是一个同步器,设计模式是模板模式。

  • 核心数据结构:双向链表 + state(锁状态)
  • 底层操作:CAS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

AQS中的int类型的state值,这里就是通过CAS(乐观锁)去修改state的值。lock的基本操作还是通过乐观锁来实现的。
获取锁通过CAS,那么没有获取到锁,等待获取锁是如何实现的?我们可以看一下else分支的逻辑,acquire方法:

  • tryAcquire:会尝试再次通过CAS获取一次锁。
  • addWaiter:通过自旋CAS,将当前线程加入上面锁的双向链表(等待队列)中。
  • acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。

可以看到,当当前线程到头部的时候,尝试CAS更新锁状态,如果更新成功表示该等待线程获取成功。从头部移除。基本可以确认,释放锁就是对AQS中的状态值State进行修改。同时更新下一个链表中的线程等待节点。
获取锁流程

可以看到在整个实现过程中,lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。目前java1.6以后,官方对synchronized做了大量的锁优化(偏向锁、自旋、轻量级锁)。因此在非必要的情况下,建议使用synchronized做同步操作。

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLockShare(共享,多个线程可同时执行,如 Semaphore/CountDownLatch)。

先大体说一下同步等待队列的特点:先进先出。获取锁失败的线程构造节点加入队列尾部,阻塞自己,等待唤醒。执行完成的线程从头部移出队列,并唤醒后续节点的线程。头结点是当前获取到锁的线程

公平锁:

non fair

非公平锁:

fair

偏向锁

锁不存在多线程竞争,并且应由一个线程多次获得锁

当线程访问同步块时,会使用 CAS 将线程 ID 更新到锁对象的 MarkWord 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。(检查thread id)

轻量级锁

当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录( LockRecord)区域,同时将锁对象的对象头中 MarkWord 拷贝到锁记录中,再尝试使用 CAS 将 MarkWord 更新为指向锁记录的指针。

如果更新成功,当前线程就获得了锁。

如果更新失败 JVM 会先检查锁对象的MarkWord 是否指向当前线程的锁记录。

如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。

不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,轻量锁就会膨胀为重量锁。

轻量锁能提升性能的原因是:认为大多数锁在整个同步周期都不存在竞争,所以使用 CAS 比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 CAS 的开销,甚至比重量锁更慢。

会自旋,否则进行锁膨胀

重量级锁

适应性自旋

在使用CAS时,如果操作失败,CAS会自旋再次尝试。由于自旋是需要消耗 CPU资源的,所以如果长期自旋就白白浪费了 CPU。 JDK1.6加入了适应性自旋:

如果某个锁自旋很少成功获得,那么下一次就会减少自旋。

ABA问题(CAS的应用:AtomicStampedReference)

  • 一个变量 int state = 0
  • 线程 A读取到 state = 0
  • 线程 B 修改state = 1 再改回 state = 0
    此时线程 A 再读取 state,还是0,但实际上已经state已经被修改过。

对于对改动敏感的变量操作,可以使用AtomicStampedReference,它会同时保存时间戳以确认是否发生改动。

参考

ThreadLocal

Map的Key是弱引用类型(WeakReference),而Value是强引用类型,如果Key被回收,Value却不会被回收。

ThreadLocalSynchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于SynchronizedThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

由于Thread包含变量ThreadLocalMap,因此ThreadLocalMapThread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。

但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。

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

参考

实现线程安全的方式

  • 加锁 利用Synchronized或者ReenTrantLock来对不安全对象进行加锁,来实现线程执行的串行化,从而保证多线程同时操作对象的安全性,一个是语法层面的互斥锁,一个是API层面的互斥锁.
  • 非阻塞同步来实现线程安全。原理就是:通俗点讲,就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生冲突,那就再采取其他措施(最常见的措施就是不断地重试,直到成功为止)。这种方法需要硬件的支持,因为我们需要操作和冲突检测这两个步骤具备原子性。通常这种指令包括CAS SC,FAI TAS等
  • 线程本地化,一种无同步的方案,就是利用Threadlocal来为每一个线程创造一个共享变量的副本来(副本之间是无关的)避免几个线程同时操作一个对象时发生线程安全问题。

阻塞队列

  • ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
  • LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE
  • PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
  • DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值