同步操作将从 dearHaoGeGe/Ebooks 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
中断和共享变量。
submit()和execute()两个方法都可以向线程池提交任务。
execute()方法的返回类型是void,它定义在Executor接口中。
submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
ABA问题:如果线程1第一次读取的值为A,线程1准备对A执行写操作,但这段时间,线程2完成了A->B->A的更改,当线程1准备写时,A已经不是原来的A。
JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。
如何解决ABA问题?
JDK1.5版本后,推出两种办法解决或改善ABA问题,分别是AtomicStampedRefence和AtomicMarkableReference。
1、AtomicStampedRefence
观察其源码发现:其内部的value被pair代替,即:
private volatile Pair<V> pair;
pair源码:
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
可以看到其内部不仅有T引用模板,还有一个int类型的stamp作为版本号,等修改时比较当前版本号与当前线程持有的版本号是否一致,若一直则修改,并stamp+1。
2、AtomicMarkableReference
观察其源码发现:其内部的value被pair代替,即:
private volatile AtomicMarkableReference.Pair<V> pair;
pair源码:
static <T> AtomicMarkableReference.Pair<T> of(T var0, boolean var1) {
return new AtomicMarkableReference.Pair(var0, var1);
}
可以看到其内部不仅有T引用模板,还有一个boolean类型的var1。
var1的值有两个true和false,修改时在这两个版本号之间来回切换,这样做并不能解决ABA问题,但可以降低其发生的几率。
解决的办法
加个版本号就可以解决ABA问题。真正要做到严谨的CAS机制,在Compare阶段不仅要比较期望值A和地址V中的实际值,还要比较变量的版本号是否一致。
用例子来说明一下,假设地址V中存储着变量值A,当前版本号是01。
线程1获得了当前值A和版本号01,想要更新为B,但是被阻塞了。
此时内存地址V中的变量发生了多次改变,版本号提升为03,但是变量值仍然是A。
随后线程1恢复运行,进行Compare操作。经过比较,线程1所获得的值和地址V的实际值都是A,但是版本号不相等,所以这一次更新失败。在Java当中AtomicStampedReference类就实现了用版本号做比较的CAS机制。
ConcurrentHashMap和Hashtable都可以用于多线程的环境,但当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。
HashTable的任何操作都会把整个表锁住,是阻塞的。好处是:总能获取最实时的更新,比如说线程A调用putAll()写入大量数据,期间线程B调用get(),线程B就会被阻塞,直到线程A完成putAll(),因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都需要排队,效率较低。
ConcurrentHashMap是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是:严格来说,读取操作不能保证反映最近的更新。例如线程A调用putAll()写入大量数据,期间线程B调用get(),则只能get()到目前为止已经顺利插入的部分数据。
JDK8的版本,与JDK6的版本有很大差异。实现线程安全的思想也已经完全变了,它摒弃了Segment(分段锁)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由数组+链表+红黑树的方式思想,但是为了做到并发,又增加了很多复制类,例如TreeBin、Traverser等对象内部类。CAS算法实现无锁化的修改至操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。
如果限定LinkedBlockingQueue的容量,当达到容量值的时候put操作和offer操作加上超时时间会阻塞。
如果没有限定容量的话,就可认为容量是无限大,直到耗尽机器的资源,发生OOM。
所以建议在使用LinkedBlockingQueue的时候最好能够设置容量,防止耗尽内容。
并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题。
比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。
Java并发程序中FutureTask表示一个可以取消的异步运算。
FutureTask有启动和取消运算、查询运算是否完成和取回运算结果等方法。
只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。
一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。
AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。
当线程池的任务缓存队列已满且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
interrupt
interrupt方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
interrupted
查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用interrupted则返回true,第二次和后面的就返回false了。
isInterrupted
仅仅是查询当前线程的中断状态。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。