我正在参加「掘金·启航计划」
一 并发编程不一定比串行快“
1. 并发会带来上下文切换、死锁、硬件和软件资源的限制。
2. 线程创建也需要时间。
二 避免死锁
1. 避免一个线程同时获取多个锁。
2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
4. 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
三 volatile 关键字
个人理解: java对变量声明 volatile时,会在编译器加上volatile关键字(C++),禁止对代码进行优化。并且每次访问强制去内存中读取,在处理器(Cpu)层面会针对不同的操作系统进行不同的指令(linux X86+内存屏障)
CPU层面
1. 有volatile变量修饰的共享变量进行写操作的时候,会有Lock前缀的指令
1.1 将当前处理器缓存行的数据写回到系统内存。
1.2 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。通过MESI协议实现和嗅探技术
四 synchronized 关键字
1.6之前称之为重量级锁。1.6长度时候进行了优化,为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁、轻量级锁,以及所得存储结构和升级过 自旋到10次或者线程达到CPU 的二分之一之上就膨胀重量级锁,可优化配置 1.6 之后就修改了 自适应
4 秒之后才会设置 偏向锁
1. 锁得三种形式
1.1 对于普通同步方法,锁是当前实例对象。
1.2 对于静态同步方法,锁是当前类的Class对象。
1.3 对于同步方法块,锁是Synchonized括号里配置的对象。
2. JVM基于进入和退出Monitor对象来实现 monitorenter 和 monitorexit
3. synchronized的锁是存在java对象头里的Mark Word中,Mark Word默认存储对象的hashCode、分代年龄和锁标记位
4. 锁状态:无锁、偏向锁、轻量级锁、重量级锁
个人理解:
1. Thread1 在访问一个同步方法并获取锁时,会在对象头和栈中的锁记录存储偏向的 Thread1 的 ID,以后该线程在进入和退出同步方法时不需要进行CAS操作来加锁和解锁,只需要测试一下对象头的Mark Word里是都存储着当前线程的偏向锁,如果成功表示线程已经获得锁。如果失败,在测试Mark Word中偏向锁的标识是否设置为1(表示当前是偏向锁):如果没有设置,则使用Cas竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
2. 偏向锁等到竞争的时候才会进行释放。会先暂停拥有偏向锁的线程,然后检查偏向锁的线程是否活着,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
3.轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。