聊聊lock和synchronized

目录

Syncchronized

1.Syncchronized简介

2.Syncchronized两个用法

1、代码块形式:手动指定锁对象

2、作为方法修饰符

3.synchronized的性质

1、原子性

2、可见性

3、有序性

4、可重入性

4.synchronized的缺陷

Lock

Lock接口介绍

使用

锁分类

lock和synchronized的区别


Syncchronized

1.Syncchronized简介

作用:同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。

即:能够保证同一时刻最多只有一个线程执行该代码,以达到保证并发安全的效果。

本质上是拿到锁,加锁,执行完成再解锁(自动挡)

地位:最基本的互斥同步手段,是并发编程中的元老级角色,是并发编程必须内容。

原理:通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁,性能较低。

2.Syncchronized两个用法

锁会不会生效取决于多个线程用的是不是同一把锁,如果用的同一把锁自然能锁住,如果用的不是同一把锁自然无法锁住。

对象锁:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)

1、代码块形式:手动指定锁对象

当 synchronized 用作代码块的修饰符时,你需要显式地指定一个锁对象。这个锁对象可以是任何对象,但是通常使用当前对象 (this) 或一个静态对象。例如:

   public class Counter {
       private int count = 0;
       private final Object lock = new Object();

       public void increment() {
           synchronized (lock) {
               count++;
           }
       }

       public int getCount() {
           synchronized (lock) {
               return count;
           }
       }
   }
   

2、作为方法修饰符

当 synchronized 用作方法的修饰符时,它会锁定当前对象(如果是实例方法)或类(如果是静态方法)。这意味着在任何给定的时间点,只有一个线程可以执行这个方法。

   public class Counter {
       private int count = 0;

       public synchronized void increment() {
           count++;
       }

       public synchronized int getCount() {
           return count;
       }
   }
   

3.synchronized的性质

1、原子性

synchronized 可以确保被它保护的代码块或方法作为一个整体执行,不会被线程中断。这意味着在 synchronized 代码块或方法内部的操作要么完全执行,要么完全不执行,不会出现部分执行的情况。
示例:

   public class Counter {
       private int count = 0;

       public synchronized void increment() {
           count++; // 这个操作是原子的,不会被其他线程中断
       }
   }
   

2、可见性

当一个线程修改了共享变量,然后释放锁,另一个线程获取锁并读取该变量时,将看到修改后的值。这是因为 synchronized 确保了修改后的变量值对所有线程的可见性。
示例:

   public class Counter {
       private int count = 0;

       public synchronized void increment() {
           count++; // 修改count
       }

       public synchronized int getCount() {
           return count; // 可见性保证了这里能读取到最新的count值
       }
   }
   

3、有序性

synchronized 还可以防止指令重排序。在没有同步的情况下,编译器和处理器可能会为了优化性能而重新排序代码指令,这可能导致多线程环境下的问题。synchronized 确保了在锁的范围内,代码的执行顺序与源代码的顺序相同。
示例:

   public class Counter {
       private boolean ready = false;
       private int count = 0;

       public synchronized void writer() {
           count = 5; // 指令1
           ready = true; // 指令2
       }

       public synchronized void reader() {
           if (ready) { // 线程B读取ready
               System.out.println(count); // 线程B读取count
           }
       }
   }
   

在上面的例子中,即使编译器或处理器试图重新排序 writer 方法中的指令,synchronized 也会阻止这种重排序,确保 ready 被设置为 true 之前 count 已经被正确赋值。


4、可重入性

synchronized 支持可重入性,即一个已经获得了锁的线程可以再次进入同一个锁保护的代码块或方法,而不会被阻塞。
示例:

   public class Counter {
       private int count = 0;

       public synchronized void increment() {
           count++;
           doSomethingElse(); // 这里可以再次调用synchronized方法,不会阻塞自己
       }

       private synchronized void doSomethingElse() {
           // ...
       }
   }
   

这些性质共同作用,使得 synchronized 成为 Java 中实现线程安全的一个重要工具。然而,过度使用 synchronized 可能会导致性能下降,因为线程在等待锁时会被阻塞。因此,在设计时应该考虑锁的粒度和范围,以平衡线程安全性和程序性能。

4.synchronized的缺陷

效率低:锁的释放情况少(执行完成或者异常)、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程

不够灵活:加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的(读写锁,只有写的时候才加锁)

无法知道是否成功获取到锁

Lock

Lock接口介绍

简介:锁是一种工具,用于控制对共享资源的访问。Lock接口最常见的实现类是ReentrantLock,通常情况下,Lock只允许一个线程来访问这个共享资源。不过有的时候,一些特殊的实现也可允许并发访问,比如ReadWriteLock里面的ReadLock。

地位&作用:Lock和synchronized,这两个是最常见的锁,他们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同。Lock并不是用来代替synchronized的,而是当使用synchronized不合适或不足以满足要求的时候,来提供高级功能的。

为什么synchronized不够用?为什么需要Lock?

(1)效率低:锁的释放情况少,试图获得锁时不能设定超时、不能中断一个正在试图获得锁的过程。

(2)不够灵活(读写锁更灵活):加锁和释放锁的时机单一,每个锁仅有的单一的条件(某个对象),可能是不够的

(3)无法知道是否成功获取到锁

使用

public interface Lock {
    
    //最普通的获取锁。如果锁已被其他线程获取,则进行等待;不会自动释放锁,建议解锁放在finally
    //不能被中断,这会带来很大的隐患:一旦陷入死锁,lock()就会陷入永久等待
    void lock();
   
   //用来尝试获取锁,如果当前锁没有被其他线程占用,则成功返回true,否则返回false,代表获取锁失败
   //相比于lock,这样的方法显然功能更强大了,我们可以根据是否能获取锁来决定后续程序的行为
   //该方法会立即返回,几遍在拿不到锁时不会一直等待
    boolean tryLock();
   
   //超时即放弃
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    //相当于tryLockk(long time, TimeUnit unit)把超时时间设置为无限,但在等待过程中,可以被中断
    void lockInterruptibly() throws InterruptedException;

    //解锁,写在finally里面
    void unlock();

}

锁分类

      

lock和synchronized的区别

Lock 接口(具体实现如 ReentrantLock)和 synchronized 关键字在 Java 中都用于实现线程同步,但它们之间有几个重要的区别:
使用方式:
synchronized 是 Java 语言的关键字,它的使用不需要显式地调用方法,可以作为代码块或方法的声明直接使用。
Lock 是一个接口,需要通过 new 关键字创建其实现类的对象,然后调用 lock() 和 unlock() 方法来控制锁的获取和释放。
灵活性:
synchronized 提供了较少的控制,例如不能指定锁的获取策略(公平锁或非公平锁)、不能在锁中进行中断响应等。
Lock 提供了更多的控制选项,比如可以选择锁的获取策略、支持尝试获取锁(tryLock)、支持锁的超时等待、允许在锁中响应中断等。
可重入性:
synchronized 默认是可重入的,即一个线程可以多次获取同一个锁。
ReentrantLock 也支持可重入性,但需要在构造函数中显式指定。
性能:
在 Java 早期版本中,synchronized 的性能可能较差,因为它涉及 JVM 层面的锁升级机制。
随着 Java 版本的更新,synchronized 的性能得到了显著优化,尤其是在 Java 6 和 Java 7 中,引入了偏向锁、轻量级锁和重量级锁的概念,使得 synchronized 的性能在许多情况下与 Lock 相当甚至更好。
死锁检测:
synchronized 不提供死锁检测机制。
使用 Lock 时,可以通过适当的编程实践和工具来检测和避免死锁。
锁状态的查询:
synchronized 不提供查询锁状态的方法。
ReentrantLock 提供了查询锁是否被当前线程持有的方法,如 isHeldByCurrentThread()。
条件变量:
synchronized 只能使用单一的 wait() 和 notify() 或 notifyAll() 方法来实现线程间的协作。
ReentrantLock 通过 Condition 对象提供了更灵活的线程间协作机制,可以有多个条件变量。
异常处理:
synchronized 块会在抛出异常时自动释放锁。
使用 Lock 时,必须在 finally 块中显式调用 unlock() 方法来释放锁,否则可能会导致锁泄漏。
选择使用 synchronized 还是 Lock 主要取决于具体的应用场景和需求。在简单场景下,synchronized 提供了更简洁的语法;而在复杂场景下,Lock 提供了更强大的控制能力和灵活性。

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值