synchronized是JVM实现同步互斥的一种方式,被synchronized修饰的代码块在代码编译后在同步代码块开始时插入monitorenter 字节码指令 ,在同步代码块结束和异常处会插入monitorexit指令。JVM会保证每一个monitorenter 都有monitorexit于之匹配,任何一个对象都有一个monitor对象和其关联,当线程执行到monitorenter 指令的时候会尝试获取对象对应monitor的全部权,也就是对象的锁,在代码块结束的或者异常的时候会释放对象对应monitor的全部权。同步方法使用的是acc_synchronized指令实现的同步。html
synchronized是重入的,当一个线程获取执行到monitorenter 指令的时候会获取对象对应的monitor的全部权,即得到锁,锁的计数器会+1。当这个线程在次获取锁的时候锁的计数器会再+1。当同步代码块结束的时候会将锁的计数器-1,直到锁的计数器为0,则释放锁的持有权。JVM以这种方式来实现synchronized的可重入性。java
同步代码块使用的是括号内的对象git
同步方法使用的是当前的实例对象程序员
静态方法使用的当前类的class对象(全局锁)github
在 Java 6 以前,Monitor 的实现彻底依赖底层操做系统的互斥锁来实现 ,若是要将一个线程进行阻塞或唤起都须要操做系统的协助,这就须要从用户态切换到内核态来执行,这种切换代价十分昂贵,很耗处理器时间,现代 JDK 中作了大量的优化。算法
偏向锁编程
当一个线程访问同步块并得到锁的时候会在对象头的Mark Word和栈帧中记录线程的ID,之后这个线程再获取锁的时候只须要比较下对象头中存储的偏向锁的ID,不须要进行CAS操做加锁和解锁便可得到锁的使用权。api
若是测试失败则须要检查下对像头Mark Word中的锁标识是不是偏向锁,若是是则尝试使用CAS将对象头中的偏向锁指向当前线程,不然使用CAS竞争锁。数组
轻量级锁bash
偏向锁运行在一个线程进入同步块的状况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁,轻量级锁的意图是在没有多线程竞争的状况下,经过CAS操做尝试将MarkWord更新为指向LockRecord的指针 。
重量级锁
虚拟机使用CAS操做尝试将MarkWord更新为指向LockRecord的指针,若是更新成功表示线程就拥有该对象的锁;若是失败,会检查MarkWord是否指向当前线程的栈帧,若是是,表示当前线程已经拥有这个锁;若是不是,说明这个锁被其余线程抢占,此时膨胀为重量级锁。
synchronized是悲观锁,它的同步策略是不管是否会有线程竞争都会加锁。CAS操做是一种乐观锁的核心算法,在执行CAS操做的时候不会挂起线程,他的核心思想是内存值,旧的预期值,新值。在提交的时候比较内存值和旧的预期值是否相等,不相等经过一个while操做新计算想要修改的新值 (自旋)。
获取锁会使该线程对应的本地内存置为失效,线程直接从主内存获取共享变量。 锁的释放会使该线程将本地内存中的共享变量写入到住内存。
锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,可是被检测到不可能存在共享数据竞争的锁进行消除。锁消除主要断定依据来源于逃逸分析的数据支持
锁粗化,若是虚拟机探测到有这样一串零碎的操做都对同一个对象加锁,将会把加锁同步的范围扩展到整个操做序列的外部,这样就只须要加锁一次就够了
sychronized是JVM原生的锁,是经过对象头设置标记实现的,ReentrantLock是基于Lock接口的实现类,经过AQS框架实现的一个可重入锁。
CountDownLatch(int count) #构造一个以给定计数CountDownLatch
await() #等待当前的计数器清零
await(long timeout, TimeUnit unit) #等待当前的计数器清零或到达超时时间
countDown() #减小锁存器的计数,若是计数达到零,释放全部等待的线程。
复制代码
CountDownLantch使用案例:并发测试工具
CyclicBarrier(int parties) #构造一个新的CyclicBarrier,拦截线程的数量是parties
CyclicBarrier(int parties, Runnable barrierAction) #构造一个新的CyclicBarrier,拦截线程的数量是parties,到达屏障时优先执行barrierAction
await() #等待
await(long timeout, TimeUnit unit) #带超时时间等待
reset() #将屏障重置为初始状态。
复制代码
Semaphore(int permits) #建立一个Semaphore与给定数量的许可证和非公平公平设置
Semaphore(int permits, boolean fair) #建立一个 Semaphore与给定数量的许可证和给定的公平设置。
acquire() #获取许可
release() #释放许可
复制代码
exchange(V x)
复制代码
CountDownLatch 是不能够重置的,因此没法重用,CyclicBarrier 没有这种限制,能够重用。CountDownLatch 通常用于一个线程等待N个线程执行完以后,再执行某种操做。CyclicBarrier 用于N个线程互相等待都达到某个状态,再执行。
ReentrantReadWriteLock 在沒有任何读写锁时,才能够取得写入锁,策略是悲观读。然而,若是读取执行状况不少,写入不多的状况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃没法竞争到锁定而一直处于等待状态。StampedLock控制锁有三种模式(写,读,乐观读 )一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字做为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被受权访问,在读锁上分为悲观锁和乐观锁。若读的操做不少,写的操做不多的状况下,你能够乐观地认为,写入与读取同时发生概率不多,所以不悲观地使用彻底的读取锁定 。
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
//下面看看乐观读锁案例
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead(); //得到一个乐观读锁
double currentX = x, currentY = y; //将两个字段读入本地局部变量
if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其余写锁发生?
stamp = sl.readLock(); //若是没有,咱们再次得到一个读悲观锁
try {
currentX = x; // 将两个字段读入本地局部变量
currentY = y; // 将两个字段读入本地局部变量
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
//下面是悲观读锁案例
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
if (ws != 0L) { //这是确认转为写锁是否成功
stamp = ws; //若是成功 替换票据
x = newX; //进行状态改变
y = newY; //进行状态改变
break;
}
else { //若是不能成功转换为写锁
sl.unlockRead(stamp); //咱们显式释放读锁
stamp = sl.writeLock(); //显式直接进行写锁 而后再经过循环再试
}
}
} finally {
sl.unlock(stamp); //释放读锁或写锁
}
}
}
复制代码
ReentrantReadWriteLock使用时要注意锁的升级和降级问题: 读写锁使用
通讯 是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通讯机制有两种:共享内存 和 消息传递。 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间经过写-读内存中的公共状态来隐式进行通讯。 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须经过明确的发送消息来显式进行通讯。 同步 是指程序用于控制不一样线程之间操做发生相对顺序的机制。 Java 的并发采用的是共享内存模型,Java 线程之间的通讯老是隐式进行,整个通讯过程对程序员彻底透明。
在 Java 中,全部实例域、静态域 和 数组元素存储在堆内存中,堆内存在线程之间共享, 局部变量、方法定义参数 和异常处理器参数 不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
JMM 定义了线程与主内存之间的抽象关系: 共享变量都存储在主内存,每条线程还有本身的工做内存,保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的全部操做(读取、赋值)都必须在工做内存中进行,不能直接读写主内存的变量。不一样的线程之间也没法直接访问对方工做内存的变量,线程间变量值的传递须要经过主内存。
对一个 volatile 变量的读,老是能看到(任意线程)对这个 volatile 变量最后的写入。对一个 volatile 变量的读会将本地内存的值置为失效从主内存获取。对一个 volatile 变量的写会将本地内存的值写入主内存。被volatile关键字修饰变量会静止指令重排序优化。被volatile关键字修饰的变量只能保证单个变量的原子性相似于 volatile++ 这种复合操做,这些操做总体上不具备原子性。
1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操做之间不能重排序。 2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操做之间不能重排序。
写final域的重排序规则能够确保:在对象引用为任意线程可见以前,对象的final域已经被正确初始化过了,而普通域不具备这个保障。
在 JMM 中,若是一个操做执行的结果须要对另外一个操做可见,那么这两个操做之间必需要存在 happens-before 关系。这里提到的两个操做既能够是在一个线程以内,也能够是在不一样线程之间。
建立/销毁线程伴随着系统开销,过于频繁的建立/销毁线程,会很大程度上影响处理效率 。
线程并发数量过多,抢占系统资源从而致使阻塞。
对线程进行一些简单的管理 。
1)AbortPolicy:直接抛出异常。
2) CallerRunsPolicy:只用调用者所在线程来运行任务。
3) DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4) DiscardPolicy:不处理,丢弃掉
若是当前运行的线程少于corePoolSize,则建立新线程来执行任务(注意,执行这一步骤须要获取全局锁)
若是运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
若是没法将任务加入BlockingQueue(队列已满),则建立新的线程来处理任务(注意,执行这一步骤须要获取全局锁)。
若是建立新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
execute():ExecutorService.execute 方法接收一个 Runable 实例,它用来执行一个任务。
submit():ExecutorService.submit() 方法返回的是 Future 对象。能够用 isDone() 来查询 Future 是否已经完成,当任务完成时,它具备一个结果,能够调用 get() 来获取结果。也能够不用 isDone() 进行检查就直接调用 get(),在这种状况下,get() 将阻塞,直至结果准备就绪。
###5.JAVA中默认的线程池有哪些?
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
复制代码
SingleThreadExecutor线程池只有一个核心线程在工做,也就是至关于单线程串行执行全部任务。若是这个惟一的线程由于异常结束,那么会有一个新的线程来替代它。此线程池保证全部任务的执行顺序按照任务的提交顺序执行。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
复制代码
FixedThreadPool 是固定大小的线程池,只有核心线程。每次提交一个任务就建立一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,若是某个线程由于执行异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
复制代码
CachedThreadPool 是无界线程池,若是线程池的大小超过了处理任务所须要的线程,那么就会回收部分空闲(60 秒不执行任务)线程,当任务数增长时,此线程池又能够智能的添加新线程来处理任务,适合执行任务时间短的异步任务。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
复制代码
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,用于处理定时任务。
scheduleAtFixedRate:该方法在initialDelay时长后第一次执行任务,之后每隔period时长,再次执行任务。注意,period是从任务开始执行算起的。开始执行任务后,定时器每隔period时长检查该任务是否完成,若是完成则再次启动任务,不然等该任务结束后才再次启动任务。
scheduleWithFixDelay:该方法在initialDelay时长后第一次执行任务,之后每当任务执行完成后,等待delay时长,再次执行任务。
##并发容器 ###1.ConcurrentHashMap 在多线程环境下,使用HashMap进行put操做时会致使链表成环。
ConcurrentHashMap的get
操做是无锁的,get
操做和put
能够同时进行,这是ConcurrentHashMap是弱一致性的根本缘由。ConcurrentHashMap的弱一致性