Amdahl 定律(阿姆达尔定律)
Gene Amdahl, 计算领域的早期先锋之一,对提升系统某一部分性能所带来的效果做出了简单却有见地的观察。这个观察被称为 Amdahl 定律 (Amdahl’s law) 。该定律的主要思想是,当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
互斥同步(Mutual Exclusion & Synchronization)
互斥同步是常见的一种并发正确性保障手段。同步:多线程并发访问共享数据时,保证共享数据同一时刻只被一个线程使用。
互斥:实现同步的一种手段。临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式。
互斥是因,同步是果。互斥是方法,同步是目的。
synchronized关键字
java中最基本的实现互斥同步的手段是synchronized关键字,它经过编译后,在同步代码块前后分别形成monitorenter和monitorexit这两个字节码指令,他们都需要一个reference对象作为参数。
如果程序中制定了,就xxx
没有指定,则:
实例方法——对象示例
类方法——类对象示例
特点:
重量级锁:java线程是映射到操作系统的原生线程之上,阻塞或唤醒线程需要操作系统完成,这需要从用户态转换到核心态,这个成本很高。
可重入锁:执行monitorenter指令时,线程需要拿对象的锁。如果没拿到,则阻塞。如果拿到,则将锁的计数器加1,再次拿到时,锁计数器继续加1。执行monitorexit时,锁计数器-1。当计数器=0,则释放锁。
java util comcurrent包
简称 J.U.C
提供ReentrantLock,也可实现互斥。
差异:
synchronized关键字 ReentrantLock
原生语法支持 API
非公平锁 默认非公平锁,可实现公平锁
可以基于wait()、notify()、notifyAll()实现一个条件
可以传入多个条件(基于Condition对象)
synchronized关键字和ReentrantLock的性能比较上,synchronized关键字经过JDK1.6优化后,性能基本持平,提倡使用关键字。
非阻塞同步
互斥同步的主要问题是线程阻塞和唤醒的性能问题,因此,被称为“阻塞同步”,是一种悲观的处理策略。”非阻塞同步“就是先进行操作,如果没有其他线程争用共享数据,则操作成功,否则采取重试等补偿措施。这种策略不会阻塞线程。
示例(基于CAS,需要CPU指令支持,保证原子操作):
while(true){
int current = get();
int next = current + 1;
if(compareAndSet(current, next)){
return next;
}
}
CAS会出现“ABA”问题
锁优化
自旋锁和自适应自旋
多处理器下,能同时让多个线程同时执行。后请求锁的线程执行忙循环等待,等待持有锁的线程释放锁。
自旋锁虽然避免了线程切换的开销,但占用处理器时间。若持有锁的线程很快释放锁,则自旋等待的效果很好。否则,就造成处理器资源浪费。因此,自旋次数必须有一定限度,用户可使用参数-XX:PreBlockSpin来更改。
虚拟机判断,如果同一个锁对象上,自旋获得锁的概率很大, 则就会延长自旋次数,否则,降低自旋次数或忽略自旋过程,这称为适应性自旋。
锁消除
虚拟机即时编译器运行时,检测到一些代码上要求同步,但实际上不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据时逃逸分析技术。
锁粗化
例如
StringBuilder sb = new StringBuilder();
sb.append(“a”);
sb.append(“b”);
sb.append(“c”);
轻量级锁
相比于使用操作系统互斥量来实现的传统锁而言的,传统锁机制称为“重量级”锁。
对象头
在代码进入同步块时,若同步对象没被锁定(锁标志位为“01”),虚拟机首先在当前线程的栈帧中建立一个名为所记录(Lock Record)的空间,存储锁对象当前的Mark Word拷贝。然后,虚拟机使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向对象的 mark word。如果成功更新,则当前线程拥有该对象锁,并将对象的Mark Word的锁标志位转化为“00”。
更新失败,则判断对象的Mark Word是否已经指向当前线程,是则直接进入同步块执行。否则,说明锁对象已被其他线程抢占。通过自旋等操作仍然不能获得锁时,那当前对象的轻量级锁就不再有效。要膨胀成重量级锁。将对象头中的Mark Word中锁标志转化为“10”,Mark Word中存储指向重量级锁(互斥量)的指针,然后线程进入阻塞状态,后面的线程也进入阻塞状态。
轻量级锁加锁过程如上文所述。解锁过程也是通过CAS完成。使用CAS操作比较替换对象的Mark Word和存在当前线程中的副本。若成功替换,则证明没有其他线程改变过对象头,则释放锁,同步完成。否则,存在其他线程竞争锁,那就要在释放锁的同时,唤醒被挂起的线程。
偏向锁
当JVM开启了偏向锁模式时,且创建对象的时机在偏向锁延迟之后时,创建的对象处于偏向锁可用且无锁状态,偏向锁标志位=1 and 锁标志位= 01,锁升级路径: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
否则,对象处于偏向锁不可用的无锁状态,偏向锁标志位=0,锁标志位=01,锁升级路径: 无锁 -> 轻量级锁 -> 重量级锁。
当大部分锁都被多个线程并发访问,则偏向锁模式会降低性能。
(1)判断偏向锁是否可用:偏向锁标志位=0 and 锁标志位=01
(2)可用,则使用偏向锁锁定。设置偏向锁标志位=1 and 锁标志位=01,使用CAS操作把当前线程ID保存到对象的Mark Word中。
(3)不可用,判断对象头的线程和当前线程ID是否一致,一致则直接执行代码。
(4)不可用且线程ID不一致。走轻量级锁的步骤。
(5)解锁:若对象被轻量级锁锁定,则