java并发机制

java并发机制底层实现原理:

多线程并不一定就快,因为线程之间切换是有上下文切换的时间的。
死锁:两个线程互相等待对方的所持有资源释放就会造成死锁。

Volatile:

Volatile并不能完全解决并发问题,比如有一个volatile的变量i,当a,b两个线程都读到了i的初始值是1,a线程进行加1操作然后回写主内存,变量变为2了,其他线程读取的时候就不会读取自己缓存的了,但是b线程现在可能也已经修改完了,也准备回写了,这时候回写的还是2。

这是volatile不适用的场景。

java并发机制_第1张图片

Volatile是有可见性的,就是当一个线程改变这个值以后,回写到主内存以后,其他线程再要操作这个变量的时候一定要从主内存中再次读取。具体实现是有通过锁总线实现的,但是锁总线开销太大,因为总线一旦被锁,cpu就不能与内存交互了。所以大多会通过缓存机制搞定,(如果这个变量不在缓存中就没办法了,就锁总线了)。当变量被修改并回写之后,根据缓存一致性,其他处理器的缓存会嗅探总线传过来的信息,如果值变了,那么所有对该变量的操作全部要重新取值,不能用线程中的副本了。

那什么时候使用volatile呢?像这种情况:

volatile boolean shutdownRequested;
...
public void shutdown(){
    shutdownRequested = true;
}
public void doWork(){
    while (!shutdownRequested){
        // do stuff
    }
}

         想要做dowork方法时,能马上通过shutdown方法去改变状态值,从而决定是否走dowork里面的逻辑。

Synchronized:
  • 重量级”锁,但是有了偏向锁和轻量级锁之后有时候也还好。
  • 对于普通同步方法,锁的是当前实例对象;
  • 对于普通静态同步方法,锁的是当前类的Class对象;
  • 对于同步方法块,锁的是括号里面的对象;
  • 锁有四种状态:从低到高依次是:无锁状态、偏向锁、轻量锁、重量锁。锁会升级但不会降级。
偏向锁:

大多数情况下不存在多线程竞争,而且锁还总是由同一个线程多次获得。所以为了减少加锁解锁的时间而引入偏向锁的概念。偏向锁的加锁:当一个线程访问同步块获取锁的时候,会在对象头的markword的记录线程ID,以后该线程再进入的时候就看markword里有没有线程ID,如果有就表示可以执行同步代码块了。偏向锁的撤销:当有其他线程竞争的时候才会撤销偏向锁,其他线程开始竞争的时候,持有锁的线程会在全局安全点(safe point)解锁,就是将markword里面的线程ID标识清空,这样其他线程就可以来使用了。偏向锁的升级:当有其他线程竞争的时候,在全局安全点的时候先暂停持锁线程,看该线程是否存活,如果挂了,那就设置成无锁状态,重新偏向;如果还存活,则检查线程的操作栈里是否还需要该锁,如果不需要则把markword清空,可以重新偏向,如果需要则升级为轻量级锁。

java并发机制_第2张图片
轻量级锁:
  • 加锁:线程执行同步块之前,JVM会先在当前线程中创建一个新的空间,把对象头中的markword复制到刚创建的空间里,然后线程尝试CAS去修改对象头中的markword为指向锁记录的指针,如果成功就获取到锁,执行同步块,如果失败,则通过自旋来获取锁。
  • 解锁:将刚才复制的原来的markword通过CAS操作修改回markword。如果有其他锁竞争资源可能会变成重量级锁。
  • 升级:当另一个线程自旋一定次数以后会升级成重量级锁;当一个线程持有锁,另一个线程竞争,又来一个,这时候也会升级成重量级锁。
java并发机制_第3张图片
锁的优缺点对比、适用场景:
  • 偏向锁:不需要加锁解锁的时间消耗;如果有线程竞争会有额外锁撤销全局暂停的时间消耗;适用于只有一个线程访问同步块,没有线程竞争的情况。
  • 轻量锁:提高程序响应速度(自旋,当释放锁的时候立即拿到);如果长时间拿不到会消耗cpu;适用于追求响应时间的,没有过多线程同时请求。
  • 重量锁:不自旋,节省cpu;线程等待,响应时间慢;追求吞吐量的,同步块里执行时间较长。

你可能感兴趣的:(Java基础)