My Life My Dream!

守信 求实 好学 力行
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Java并发控制(synchronized、ReentrantLock、CountDownLatch)

Posted on 2022-05-12 09:56  召冠  阅读(424)  评论(0编辑  收藏  举报

参考资料:

https://blog.csdn.net/zcl_love_wx/article/details/93977947

https://zhuanlan.zhihu.com/p/420560153

 

Java中锁的实现方式有两种:synchronized关键字和并发包中的锁类。synchronized这个同步关键字以前性能不是太理想,在随着不停的优化后,它已经成了同步的首选。

 

锁涉及的几个重要概念:重入锁、自旋锁、锁偏向、锁粗化、公平锁与非公平锁、轻量级锁与重量级锁、读写锁与独占锁(乐观锁与悲观锁)

synchronized、ReentrantLock 都属于可重入锁。

synchronized、ReentrantLock 都是悲观锁。

synchronized,ReentrantLock 都是独占锁。

synchronized 是重量锁。

synchronized 是非公平锁,ReentrantLock可以通过构造函数指定该锁是公平的还是非公平的,默认是非公平的。

 

问题1: 可重入锁如果加了两把,但是只释放了一把会出现什么问题?

答:程序卡死,线程不能出来,也就是说我们申请了几把锁,就需要释放几把锁。

问题2: 如果只加了一把锁,释放两次会出现什么问题?

答:会报错,java.lang.IllegalMonitorStateException。

 

CountDownLatch 与 CyclicBarrier

CountDownLatch 它是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

CyclicBarrier 也是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。

 

类锁和对象锁(重要)

Class A {
     // ==>对象锁:普通实例方法默认同步监视器就是this,
     // 即调用该方法的对象
     public synchronized methodA() {
     }

    public  methodB() {    
         // ==>对象锁:this表示是对象锁
         synchronized(this){ 
         }
     }

    // ==>类锁:修饰静态方法
     public static synchronized methodC() {
     }

    public methodD(){
         // ==>类锁:A.class说明是类锁
         synchronized(A.class){}
     }

    // 普通方法:任何情况下调用时,都不会发生竞争
     public common(){
     }
}

 

类锁的5种形式

Class A {
     // 普通字符串属性
     private String val;
     // 静态属性
     private static Object staticObj;

     // ==>类锁情况1:synchronized修饰静态方法
     public static synchronized methodA() {
     }

    public methodB(){
         // ==>类锁情况2:同步块里的对象是类
         synchronized(A.class){}
     }

     public methodC(){
          // ==>类锁情况3:同步块里的对象是字符串
         synchronized("ABC"){}
     }

    public methodD(){
         // ==>类锁情况4:同步块里的对象是静态属性
         synchronized(staticObj){}
     }

    public methodE(){
         // ==>类锁情况5:同步块里的对象是字符串属性
         synchronized(val){}
     }
}

 

 

锁粗化也提醒了我们平时写代码时,尽量不要在循环内使用锁:

// 粗化前
for(int i=0;i<10000;i++){
     // 这会导致频繁同步代码,无谓的消耗系统资源
     synchronized(monitor){
         doSomething...
     }
}
// 粗化后
synchronized(monitor){
     for(int i=0;i<10000;i++){   
         doSomething...
     }
}

 

锁偏向

偏向锁指的是,当第一个线程请求时,会判断锁的对象头里的ThreadId字段的值,如果为空,则让该线程持有偏向锁,并将ThreadId的值置为当前线程ID。当前线程再次进入时,如果线程ID与ThreadId的值相等,则该线程就不会再重复获取锁了。因为锁的请求与释放是要消耗系统资源的。

如果有其他线程也来请求该锁,则偏向锁就会撤销,然后升级为轻量级锁。如果锁的竞争十分激烈,则轻量级锁又会升级为重量级锁。