1、 同步程序员
1 synchronized 关键字编程
synchronized 锁什么?锁对象。缓存
可能锁对象包括: this, 临界资源对象,Class 类对象。多线程
1.1 同步方法并发
synchronized T methodName(){}
同步方法锁定的是当前对象。当多线程经过同一个对象引用屡次调用当前同步方法时,需同步执行。优化
1.2 同步代码块this
同步代码块的同步粒度更加细致,是商业开发中推荐的编程方式。能够定位到具体的同步位置,而不是简单的将方法总体实现同步逻辑。在效率上,相对更高。atom
1.2.1 锁定临界对象线程
T methodName(){ synchronized(object){} }
同步代码块在执行时,是锁定 object 对象。当多个线程调用同一个方法时,锁定对象不变的状况下,需同步执行。指针
1.2.2 锁定当前对象
T methodName(){ synchronized(this){} }
当锁定对象为 this 时,至关于 1.1 同步方法。
1.3 锁的底层实现
Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。同步方法 并非由 monitor enter 和 monitor exit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的。
1.3.1 对象内存简图
对象头:存储对象的 hashCode、锁信息或分代年龄或 GC 标志,类型指针指向对象的类元数据,JVM 经过这个指针肯定该对象是哪一个类的实例等信息。
实例变量:存放类的属性数据信息,包括父类的属性信息
填充数据:因为虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
当在对象上加锁时,数据是记录在对象头中。当执行 synchronized 同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向的是 monitor 对象(也称为管程或监视器锁)的起始地址。每一个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 能够与对象一块儿建立销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。
在 Java 虚拟机(HotSpot)中,monitor 是由 ObjectMonitor 实现的。
ObjectMonitor 中有两个队列,_WaitSet 和 _EntryList,以及_Owner 标记。其中_WaitSet 是用于管理等待队列(wait)线程的,_EntryList 是用于管理锁池阻塞线程的,_Owner 标记用于记录当前执行线程。线程状态图以下:
当多线程并发访问同一个同步代码时,首先会进入_EntryList,当线程获取锁标记后, monitor 中的_Owner 记录此线程,并在 monitor 中的计数器执行递增计算(+1),表明锁定,其余线程在_EntryList 中继续阻塞。若执行线程调用 wait 方法,则 monitor 中的计数器执行赋值为 0 计算,并将_Owner 标记赋值为 null,表明放弃锁,执行线程进如_WaitSet 中阻塞。
若执行线程调用 notify/notifyAll 方法,_WaitSet 中的线程被唤醒,进入_EntryList 中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,一样会释放锁标记,monitor 中的_Owner 标记赋值为 null,且计数器赋值为 0 计算。
1.4 锁的种类
Java 中锁的种类大体分为偏向锁,自旋锁,轻量级锁,重量级锁。
锁的使用方式为:先提供偏向锁,若是不知足的时候,升级为轻量级锁,再不知足,升级为重量级锁。自旋锁是一个过渡的锁状态,不是一种实际的锁类型。
锁只能升级,不能降级。
1.4.1 重量级锁
在 1.3 中解释的就是重量级锁。
1.4.2 偏向锁
是一种编译解释锁。若是代码中不可能出现多线程并发争抢同一个锁的时候,JVM 编译代码,解释执行的时候,会自动的放弃同步信息。消除 synchronized 的同步代码结果。使用锁标记的形式记录锁状态。在 Monitor 中有变量 ACC_SYNCHRONIZED。当变量值使用的时候,表明偏向锁锁定。能够避免锁的争抢和锁池状态的维护。提升效率。
1.4.3 轻量级锁
过渡锁。当偏向锁不知足,也就是有多线程并发访问,锁定同一个对象的时候,先提高为轻量级锁。也是使用标记 ACC_SYNCHRONIZED 标记记录的。ACC_UNSYNCHRONIZED 标记记录未获取到锁信息的线程。就是只有两个线程争抢锁标记的时候,优先使用轻量级锁。
两个线程也可能出现重量级锁。
1.4.4 自旋锁
是一个过渡锁,是偏向锁和轻量级锁的过渡。
当获取锁的过程当中,未获取到。为了提升效率,JVM 自动执行若干次空循环,再次申请锁,而不是进入阻塞状态的状况。称为自旋锁。自旋锁提升效率就是避免线程状态的变动。
2 volatile 关键字
变量的线程可见性。在 CPU 计算过程当中,会将计算过程须要的数据加载到 CPU 计算缓存中,当 CPU 计算中断时,有可能刷新缓存,从新读取内存中的数据。在线程运行的过程当中,若是某变量被其余线程修改,可能形成数据不一致的状况,从而致使结果错误。而 volatile 修饰的变量是线程可见的,当 JVM 解释 volatile 修饰的变量时,会通知 CPU,在计算过程当中,每次使用变量参与计算时,都会检查内存中的数据是否发生变化,而不是一直使用 CPU 缓存中的数据,能够保证计算结果的正确。
volatile 只是通知底层计算时,CPU 检查内存数据,而不是让一个变量在多个线程中同步。
3 wait¬ify
4 AtomicXxx 类型组
原子类型。
在 concurrent.atomic 包中定义了若干原子类型,这些类型中的每一个方法都是保证了原子操做的。多线程并发访问原子类型对象中的方法,不会出现数据错误。在多线程开发中,若是某数据须要多个线程同时操做,且要求计算原子性,能够考虑使用原子类型对象。
注意:原子类型中的方法是保证了原子操做,但多个方法之间是没有原子性的。如:
AtomicInteger i = new AtomicInteger(0); if(i.get() != 5) i.incrementAndGet();
在上述代码中,get 方法和incrementAndGet 方法都是原子操做,但复合使用时,没法
保证原子性,仍旧可能出现数据错误。
5 CountDownLatch 门闩
门闩是 concurrent 包中定义的一个类型,是用于多线程通信的一个辅助类型。
门闩至关于在一个门上加多个锁,当线程调用 await 方法时,会检查门闩数量,若是门闩数量大于 0,线程会阻塞等待。当线程调用 countDown 时,会递减门闩的数量,当门闩数量为 0 时,await 阻塞线程可执行。
6 锁的重入
在 Java 中,同步锁是能够重入的。只有同一线程调用同步方法或执行同步代码块,对同一个对象加锁时才可重入。
当线程持有锁时,会在 monitor 的计数器中执行递增计算,若当前线程调用其余同步代码,且同步代码的锁对象相同时,monitor 中的计数器继续递增。每一个同步代码执行结束, monitor 中的计数器都会递减,直至全部同步代码执行结束,monitor 中的计数器为 0 时,释放锁标记,_Owner 标记赋值为 null。
7 ReentrantLock
重入锁,建议应用的同步方式。相对效率比 synchronized 高。量级较轻。
synchronized 在 JDK1.5 版本开始,尝试优化。到 JDK1.7 版本后,优化效率已经很是好了。
在绝对效率上,不比 reentrantLock 差多少。
使用重入锁,必须必须必须手工释放锁标记。通常都是在 finally 代码块中定义释放锁标记的 unlock 方法。
7.1 公平锁
8 ThreadLocal
你说啥?你想要视频教程文字看不懂?你还想要其余视频教程?那你赶忙关注公众号程序员理想吧