当前位置:首页 > 聊聊并发 - 图文
要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。下图中的线程1演示了偏向锁初始化的流程,线程2演示了偏向锁撤销的流程。
关闭偏向锁:偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟-XX:BiasedLockingStartupDelay = 0。如果你确定自己应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁-XX:-UseBiasedLocking=false,那么默认会进入轻量级锁状态。
4.4 轻量级锁
轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。
然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。 轻量级锁解锁:轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。
5 锁的优缺点对比
锁 偏向锁 优点 加锁和解锁不需要额缺点 如果线程间存在锁竞争,会适用场景 适用于只有一个线程外的消耗,和执行非同步方法比仅存在纳秒级的差距。 轻量级锁 带来额外的锁撤销的消耗。 访问同步块场景。 竞争的线程不会阻塞,如果始终得不到锁竞争的追求响应时间。 提高了程序的响应速线程使用自旋会消耗CPU。 同步块执行速度非常度。 快。 线程竞争不使用自旋,线程阻塞,响应时间缓慢。 追求吞吐量。 不会消耗CPU。 同步块执行速度较长。 重量级锁 6 参考源码 本文一些内容参考了HotSpot源码 。对象头源码markOop.hpp。偏向锁源码biasedLocking.cpp。以及其他源码ObjectMonitor.cpp和BasicLock.cpp。 7 参考资料 ? ? ? ? ? ? ? ? 偏向锁 java-overview-and-java-se6 Synchronization Optimization章节 Dave Dice “Synchronization in Java SE 6” Java SE 6 Performance White Paper 2.1章节 JVM规范(Java SE 7) Java语言规范(JAVA SE7) 周志明的《深入理解Java虚拟机》 Java偏向锁实现原理 作者简介 方腾飞,阿里巴巴资深软件开发工程师,致力于高性能网络和并发编程,目前在公司从事询盘管理和长连接服务器OpenComet的开发工作。 博客地址:http://ifeve.com 微博地址:http://weibo.com/kirals
聊聊并发(三)——JAVA线程池的分析和使用
http://www.infoq.com/cn/articles/java-threadPool
1. 引言
合理利用线程池能够带来三个好处。第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。
2. 线程池的使用
线程池的创建
我们可以通过ThreadPoolExecutor来创建一个线程池。
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler); 创建一个线程池需要输入几个参数:
?
corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。
o ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先
进先出)原则对元素进行排序。
o LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先
出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
o SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个
线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
o PriorityBlockingQueue:一个具有优先级的无限阻塞队列。 maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。 o AbortPolicy:直接抛出异常。
o CallerRunsPolicy:只用调用者所在线程来运行任务。
o DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 o DiscardPolicy:不处理,丢弃掉。
o 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。
如记录日志或持久化不能处理的任务。 keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
?
?
? ?
?
共分享92篇相关文档