多线程的缓存数据须要放到chm,修改缓存数据,多线程计数统计数据都须要使用锁java
synchronized的使用编程
- 在多线程并发编程中synchronized一直是元老级角色,不少人都会称呼它为重量级锁。
- 可是,随着Java SE 1.6对synchronized进行了各类优化以后,
- 有些状况下它就并不那么重了,
- Java SE 1.6中为了减小得到锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,
- 以及锁的存储结构和升级过程。
- 咱们仍然沿用前面使用的案例,而后经过synchronized关键字来修饰在inc的方法上。
- 再看看执行结果:
synchronized的三种应用方式缓存
- synchronized有三种方式来加锁,分别是
- 修饰实例方法,做用于当前实例加锁,
- 静态方法,做用于当前类对象加锁,
- 修饰代码块,指定加锁对象,对给定对象加锁,
synchronized括号后面的对象多线程
- synchronized括号里面的对象是一把锁,
- 在java中任意一个对象均可以成为锁,
- 简单来讲,咱们把object比喻是一个key,
- 拥有这个key的线程才能执行这个方法,
- 拿到这个key之后在执行方法过程当中,这个key是随身携带的,而且只有一把。
- 若是后续的线程想访问当前方法,
- 由于没有key因此不能访问只能在门口等着,
- 等以前的线程把key放回去。
- 因此,synchronized锁定的对象必须是同一个,
- 若是是不一样对象,就意味着是不一样的房间的钥匙,
- 对于访问者来讲是没有任何影响的
synchronized的字节码指令并发
- 经过javap -v 来查看对应代码的字节码指令,
- 对于同步块的实现使用了monitorenter和monitorexit指令,
- 前面咱们在讲JMM的时候,提到过这两个指令,他们隐式的执行了Lock和UnLock操做,用于提供原子性保证。
- monitorenter指令插入到同步代码块开始的位置、
- monitorexit指令插入到同步代码块结束位置,
- jvm须要保证每一个monitorenter都有一个monitorexit对应。
- 这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,
- 这个过程是排他的,
- 也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器线程
- 执行到monitorenter指令时,
- 会尝试获取对象所对应的monitor全部权,也就是尝试获取对象的锁;
- 而执行monitorexit,就是释放monitor的全部权
synchronized的锁的原理jvm
- jdk1.6之后对synchronized锁进行了优化,包含偏向锁、轻量级锁、重量级锁;
- 在了解synchronized锁以前,咱们须要了解两个重要的概念,一个是对象头、另外一个是monitor
Java对象头ide
- 在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充;
- Java对象头是实现synchronized的锁对象的基础,
- 通常而言,synchronized使用的锁对象是存储在Java对象头里。
- 它是轻量级锁和偏向锁的关键
- 锁的全部信息,都记录在java的对象头中
Mark Word用于存储对象自身的运行时数据工具
- 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。
- Java对象头通常占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit)

Monitoroop
- 什么是Monitor?
- 咱们能够把它理解为一个同步工具,也能够描述为一种同步机制。
- 全部的Java对象是天生的Monitor,每一个object的对象里 markOop->monitor() 里能够保存ObjectMonitor的对象。
- 从源码层面分析一下monitor对象
- Ø oop.hpp下的oopDesc类是JVM对象的顶级基类,
- Ø markOop.hpp**中** markOopDesc继承自oopDesc,
- 并扩展了本身的monitor方法,
- 这个方法返回一个ObjectMonitor指针对象
- Ø objectMonitor.hpp,在hotspot虚拟机中,
- 采用ObjectMonitor类来实现monitor,
synchronized的锁升级和获取过程布局
- 了解了对象头以及monitor之后,接下来去分析synchronized的锁的实现,就会很是简单了。
- 前面讲过synchronized的锁是进行过优化的,引入了偏向锁、轻量级锁;
- 锁的级别从低到高逐步升级, 无锁->偏向锁->轻量级锁->重量级锁.
- 而且锁只能升级不能降级
自旋锁(CAS)
- 自旋锁就是让不知足条件的线程等待一段时间,而不是当即挂起。
- 怎么自旋呢?
- 其实就是一段没有任何意义的循环。
- 虽然它经过占用处理器的时间来避免线程切换带来的开销,
- 可是若是持有锁的线程不能在很快释放锁,
- 那么自旋的线程就会浪费处理器的资源,
- 由于它不会作任何有意义的工做。
- 因此,自旋等待的时间或者次数是有一个限度的,
- 若是自旋超过了定义的时间仍然没有获取到锁,则该线程应该被挂起
偏向锁
- 大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,
- 为了让线程得到锁的代价更低而引入了偏向锁。
- 当一个线程访问同步块并获取锁时,
- 会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,
- 之后该线程在进入和退出同步块时不须要进行CAS操做来加锁和解锁,
- 只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
- 若是测试成功,表示线程已经得到了锁。
- 若是测试失败,则须要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):
- 若是没有设置,则使用CAS竞争锁;
- 若是设置了,则尝试使用CAS将对象头的偏向锁指向当前线程
轻量级锁
- 引入轻量级锁的主要目的是在没有多线程竞争的前提下,
- 减小传统的重量级锁使用操做系统互斥量产生的性能消耗。
- 当关闭偏向锁功能或者多个线程竞争偏向锁致使偏向锁升级为轻量级锁,则会尝试获取轻量级锁
- 轻量锁与偏向锁不一样的是:
1. 轻量级锁每次退出同步块都须要释放锁,而偏向锁是在竞争发生时才释放锁
2. 每次进入退出同步块都须要CAS更新对象头
3. 争夺轻量级锁失败时,自旋尝试抢占锁
重量级锁
- 当竞争线程尝试占用轻量级锁失败屡次以后,
- 轻量级锁就会膨胀为重量级锁,
- 重量级线程指针指向竞争线程,竞争线程也会阻塞,
- 等待轻量级线程释放锁后唤醒他。
- 重量级锁经过对象内部的监视器(monitor)实现,
- 其中monitor的本质是依赖于底层操做系统的Mutex Lock实现,
- 操做系统实现线程之间的切换须要从用户态到内核态的切换,切换成本很是高。
- 前面咱们在讲Java对象头的时候,讲到了monitor这个对象,
- 在hotspot虚拟机中,经过ObjectMonitor类来实现monitor。
- 他的锁的获取过程的体现会简单不少
- 与轻量级锁区别区别是:
- 竞争失败后,线程阻塞,
- 释放锁后,唤醒阻塞的线程,
- 不使用自旋锁,不会那么消耗CPU,
- 因此重量级锁适合用在同步块执行时间长的状况下。

wait和notify
- wait和notify是用来让线程进入等待状态以及使得线程唤醒的两个操做
public class ThreadWait extends Thread{
private Object lock;
public ThreadWait(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
System.out.println("开始执行 thread wait");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行结束 thread wait");
}
}
}
public class ThreadNotify extends Thread{
private Object lock;
public ThreadNotify(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
System.out.println("开始执行 thread notify");
lock.notify();
System.out.println("执行结束 thread notify");
}
}
}
wait和notify的原理
- 调用wait方法,首先会获取监视器锁,
- 得到成功之后,会让当前线程进入等待状态进入等待队列而且释放锁;
- 而后当其余线程调用notify或者notifyall之后,会选择从等待队列中唤醒任意一个线程,
- 而执行完notify方法之后,并不会立马唤醒线程,
- 缘由是当前的线程仍然持有这把锁,处于等待状态的线程没法得到锁。
- 必需要等到当前的线程执行完按monitorexit指令之后,也就是锁被释放之后,处于等待队列中的线程就能够开始竞争锁了

wait和notify为何须要在synchronized里面
- wait方法的语义有两个,
- 一个是释放当前的对象锁、

- 执行结果:
- 另外一个是使得当前线程进入阻塞队列,
- 而这些操做都和监视器是相关的,
- 因此wait必需要得到一个监视器锁
- 而对于notify来讲也是同样,它是唤醒一个线程,
- 既然要去唤醒,首先得知道它在哪里?
- 因此就必需要找到这个对象获取到这个对象的锁,
- 而后到这个对象的等待队列中去唤醒一个线程。