聊聊java的两种锁同步锁和重入锁

2023-12-07 13:36:55 浏览数 (1)

java重入锁和同步锁有什么区别

在Java中,重入锁(ReentrantLock)和同步锁(Synchronized)都是用于实现线程同步的机制,但它们有一些区别。

  1. 可重入性:
    • 重入锁是可重入的,也就是说,同一个线程可以多次获取同一个重入锁而不会产生死锁。在获取锁之后,线程可以多次进入被保护的代码块,并且每次退出代码块时都要释放锁。
    • 同步锁也是可重入的。当一个线程获取到同步锁后,可以再次获取同一个锁而不会产生死锁。同步锁的可重入性是由Java虚拟机自动实现的。
  1. 锁的获取方式:
    • 重入锁需要手动获取和释放,即通过调用lock()方法获取锁,然后在合适的时候调用unlock()方法释放锁。
    • 同步锁是隐式获取和释放的,当线程进入同步代码块时,会自动获取同步锁;当线程退出同步代码块时,会自动释放同步锁。
  1. 粒度:
    • 重入锁提供了更细粒度的控制。可以通过使用tryLock()方法尝试获取锁、使用lockInterruptibly()方法支持可中断的获取锁等。
    • 同步锁相对来说粒度较大,只能使用synchronized关键字来修饰代码块或方法。
  1. 灵活性:
    • 重入锁提供了一些高级功能,如公平性设置、条件变量等,可以更灵活地控制线程的访问顺序和条件等待。
    • 同步锁相对简单,没有提供类似的高级功能。

什么场景用重入锁好

重入锁(ReentrantLock)在以下场景中通常比同步锁(Synchronized)更适用:

  1. 公平性要求:重入锁可以通过构造函数的参数设置为公平锁,从而按照线程请求锁的顺序来获取锁。而同步锁无法直接实现公平性,它总是采用非公平的方式获取锁。
  2. 可中断性要求:重入锁提供了lockInterruptibly()方法,可以在获取锁的过程中响应中断请求。而同步锁在获取锁的过程中无法响应中断请求,只能等待获取锁。
  3. 尝试获取锁:重入锁提供了tryLock()方法,可以尝试获取锁而不会发生阻塞。该方法可以用于实现一些特殊的业务逻辑,例如尝试获取锁一段时间后放弃。
  4. 多个条件变量:重入锁可以使用newCondition()方法创建多个条件变量,可以更灵活地实现线程之间的通信和协作。同步锁只能使用wait()notify()方法进行线程的等待和唤醒。

总的来说,如果需要更高级的功能、更细粒度的控制、公平性要求或可中断性要求,重入锁是一个更好的选择。但在一些简单的同步场景中,同步锁通常更加方便和易用。

重入锁如何锁类

重入锁(ReentrantLock)提供了一个条件变量(Condition)机制,可以用于线程之间的通信和协作。下面是一个示例:

代码语言:javascript复制
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

        new Thread(()->{
// 线程1
            lock.lock();
            try {
                System.out.println("1");
                // 执行一些操作
                while (true) {
                    System.out.println("1 s");
                    condition.await(); // 等待条件变量
                    System.out.println("1 e");
                }
                // 执行其他操作
            } catch (InterruptedException e) {
                System.out.println("1 InterruptedException");
                throw new RuntimeException(e);
    
            } finally {
                lock.unlock();
                System.out.println("1 end");
    
            }
        }).start();

        Thread.sleep(1000);
        new Thread(()->{
// 线程2
            lock.lock();
            try {
                System.out.println("2");
                // 执行一些操作
                condition.signal(); // 唤醒线程1
                System.out.println("2 end");
            } finally {
                lock.unlock();
            }
        }).start();
        Thread.currentThread().join();

在上述示例中,我们创建了一个ReentrantLock对象lock和一个条件变量condition。在线程1中,我们获取锁并执行一些操作,然后在满足某个条件时,调用await()方法等待条件变量。在线程2中,我们获取锁并执行一些操作,然后设置条件变量并调用signal()方法唤醒线程1。

这样就可以使用条件变量来实现线程之间的通信和协作。需要注意的是,在使用条件变量时,需要先获取锁并在try-finally块中释放锁,以确保在任何情况下都能正确释放锁。

此外,需要注意的是,在等待条件变量时,应该总是使用while循环来检查条件是否满足,而不是使用if语句。这是因为,在多线程环境中,可能会出现虚假唤醒的情况,即线程在没有收到信号的情况下被唤醒。使用while循环可以避免这种情况。

同步锁如何锁类

在Java中,可以使用synchronized关键字来锁住类。具体来说,可以在静态方法或静态代码块中使用synchronized关键字来锁住类。下面是一个示例:

代码语言:javascript复制
public class MyClass {
    // 静态变量
    private static int count = 0;

    // 静态方法
    public static synchronized void increment() {
        count  ;
    }

    // 静态代码块
    static {
        synchronized(MyClass.class) {
            // 执行需要同步的代码块
            // ...
        }
    }
}

在上述示例中,我们定义了一个类MyClass,其中包含一个静态变量count和一个静态方法increment()。在increment()方法中,我们使用synchronized关键字来锁住该方法,以确保在同一时刻只有一个线程可以访问该方法。

另外,在静态代码块中,我们使用synchronized关键字来锁住类对象MyClass.class,以确保在同一时刻只有一个线程可以执行静态代码块中的代码。

这样就可以使用synchronized关键字来锁住类,确保在同一时刻只有一个线程可以访问被保护的代码块。需要注意的是,在实际使用中,需要谨慎使用类锁,以避免死锁或性能问题等,当然也可以锁住某个对象。

代码语言:javascript复制
    public static void main(String[] args) {
        Map<String, String> list = new HashMap<String, String>();

        for (int i = 0; i < 5; i  ) {
            String str = new String("hello"   i);
            if (list.containsKey(str)) {
                System.out.println(str "存在");
            } else {
                String clock = new String("123");
                list.put(str, clock);
            }
        }
        System.out.println(Json.getJsonFromObject(list));
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i  ) {
            String str = new String("hello"   i);
            executorService.execute(() -> {
                String s = list.get(str);
                System.out.println(s   "========"   str);
                synchronized (s) {
                    System.out.println(str   "获得锁");
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(str   "释放锁");
            });
        }
        System.out.println();
    }

在如上的代码示例中,你可以看到使用同步锁来锁住某个固定的对象,以达到细粒度锁的功能。

最后

总体来说,重入锁相对于同步锁提供了更多的灵活性和控制能力,但使用起来也更加复杂。在大部分情况下,使用同步锁已经足够满足线程同步的需求。只有在需要更高级的功能或更细粒度控制时,才需要考虑使用重入锁。

点赞关注评论一键三连,每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

0 人点赞