大厂面试题详解:synchronized的偏向锁和自旋锁怎么实现的

简介: 字节跳动大厂面试题详解:synchronized的偏向锁和自旋锁怎么实现的

大厂面试题详解:synchronized的偏向锁和自旋锁怎么实现的


理解 synchronized 关键字


在 Java 中,synchronized 关键字是实现并发控制的重要工具之一。它用于实现对共享资源的互斥访问,确保在同一时刻只有一个线程可以进入同步代码块或方法。


synchronized 的基本用法

public class SynchronizedExample {
    private int count = 0;

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

在上述示例中,increment() 方法被 synchronized 修饰,保证了对 count 的操作是原子的,即线程安全的。


synchronized 的性能问题


传统的 synchronized 在竞争激烈的情况下可能会导致性能问题,因为它会涉及到线程的上下文切换和用户态与内核态的切换。


偏向锁的引入


为了解决 synchronized 在单线程情况下的性能问题,JDK 1.6 引入了偏向锁的概念。偏向锁是一种针对单线程场景进行优化的锁机制,它可以减少无竞争情况下的同步操作开销。


偏向锁的实现原理

public class SynchronizedExample {
    private int count = 0;

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


在单线程访问的情况下,synchronized 会尝试偏向于这个线程。当有其他线程尝试访问这个锁时,偏向锁就会升级为常规锁,保证多线程下的正确性。


自旋锁的引入


自旋锁是一种基于循环等待的锁,线程在获取锁时不会被挂起,而是不断地尝试获取锁。


自旋锁的实现原理

import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLockExample {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待锁释放
        }
    }

    public void unlock() {
        locked.set(false);
    }
}


在自旋等待期间,线程会一直占用 CPU 资源,如果等待时间过长,会导致性能问题。因此,自旋锁适用于短时间内持有锁的情况。


应用场景和最佳实践


synchronized 的适用场景


  • 单线程访问或轻量级的并发情况下,可以使用 synchronized,它简单易用,且性能较好。
  • 对于竞争激烈的场景或高并发环境,考虑使用 ReentrantLock 或者 ConcurrentHashMap 等并发工具类。


偏向锁和自旋锁的应用场景


  • 偏向锁适用于单线程访问的场景,可以提升同步操作的性能。
  • 自旋锁适用于短时间内持有锁的情况,可以减少线程上下文切换的开销。


高级应用和性能优化


偏向锁的优化


偏向锁在单线程场景下提供了很好的性能优化,但在多线程竞争激烈的情况下,偏向锁的性能会下降。为了解决这个问题,JVM 会根据一定的规则自动取消偏向锁。


偏向锁的实现机制涉及到锁标识位的设置与撤销,其主要流程包括偏向锁的获取、撤销和升级。


示例代码:

public class BiasedLockExample {
    private int value = 0;

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

上述代码中的 increment() 方法使用了 synchronized 关键字,这意味着该方法是一个同步方法,会使用偏向锁进行优化。


自旋锁的优化


自旋锁的性能受到 CPU 核心数和线程竞争情况的影响。当线程竞争不激烈或锁持有时间较短时,自旋锁可以提升性能。但如果自旋等待时间过长,会造成资源浪费。


自旋锁通过循环重试的方式尝试获取锁,避免了线程阻塞和切换的开销。


示例代码:

import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLock {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待锁释放
        }
    }

    public void unlock() {
        locked.set(false);
    }
}

上述代码中,lock() 方法通过 AtomicBoolean 类型的状态标识来实现自旋锁的获取,unlock() 方法用于释放锁。


使用 synchronized 关键字


尽管 ReentrantLock 提供了更多的灵活性和功能,但在绝大多数情况下,synchronized 关键字已经能够满足需求,并且使用更为简单和方便。


synchronized 关键字的使用简单直接,JVM 对其进行了高度优化,性能较为优秀。在绝大多数情况下,synchronized 能够满足锁的需求,并且使用更为简单和方便。


示例代码:

public class SynchronizedExample {
    private int count = 0;

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

上述代码中的 increment() 方法使用了 synchronized 关键字,保证了方法的原子性,避免了多线程并发访问导致的数据不一致问题。


应用场景


偏向锁的应用场景: 偏向锁适用于存在大量读线程的场景,通过偏向锁可以减少无谓的竞争,提高性能。


假设有一个数据结构,被多个线程读取,但只有少数线程会修改数据。在这种场景下,偏向锁可以提供显著的性能优势,因为大多数情况下读线程无需争夺锁,可以直接访问数据,而修改数据的线程会在获得锁后进行修改。

/**
 * 偏向锁的应用场景:
 * 偏向锁适用于存在大量读线程的场景,通过偏向锁可以减少无谓的竞争,提高性能。
 * 
 * 假设有一个数据结构,被多个线程读取,但只有少数线程会修改数据。在这种场景下,偏向锁可以提供显著的性能优势,
 * 因为大多数情况下读线程无需争夺锁,可以直接访问数据,而修改数据的线程会在获得锁后进行修改。
 */

public class BiasedLockExample {
    // 定义共享的数据
    private int data = 0;

    /**
     * 读取数据的方法
     */
    public synchronized void readData() {
        System.out.println("Reading data: " + data);
    }

    /**
     * 修改数据的方法
     * @param newData 新数据
     */
    public synchronized void modifyData(int newData) {
        // 修改数据
        data = newData;
        System.out.println("Data modified to: " + data);
    }
}

在上述代码中,我展示了一个简单的应用场景,适合使用偏向锁进行性能优化。在这个示例中,多个线程可以同时调用 readData() 方法读取数据,而只有在调用 modifyData(int newData) 方法时才需要竞争偏向锁进行数据修改。


通过使用偏向锁,大多数情况下读取数据的线程无需竞争锁资源,可以直接访问数据,而修改数据的线程则会在获得锁之后进行数据修改操作,有效减少了竞争和锁的开销,提高了系统的性能和吞吐量。


自旋锁的应用场景: 自旋锁适用于锁的持有时间短、线程并发数不高的场景,可以有效减少线程阻塞和切换带来的开销。


自旋锁适用于并发竞争不激烈、锁竞争时间较短的场景,例如某个资源被多个线程轮流访问的情况,可以有效减少线程切换的开销。

/**
 * 自旋锁的实现:
 * 自旋锁是一种基于循环等待的锁,线程在获取锁时不会被挂起,而是不断地尝试获取锁。
 * 
 * 自旋锁适用于锁的持有时间较短、线程竞争不激烈的情况下,可以有效减少线程阻塞和切换带来的开销。
 */

import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLock {
    // 使用AtomicBoolean作为状态标志,初始化为false表示未锁定状态
    private AtomicBoolean locked = new AtomicBoolean(false);

    /**
     * 加锁方法,使用自旋等待获取锁
     */
    public void lock() {
        // 使用CAS操作尝试获取锁,直到成功为止
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待锁释放
            // 在自旋过程中,当前线程不会被挂起,而是一直循环尝试获取锁
        }
    }

    /**
     * 解锁方法,将锁标志位设置为false
     */
    public void unlock() {
        // 将锁标志位设置为false,表示释放锁
        locked.set(false);
    }
}

在上述代码中,我实现了一个简单的自旋锁,采用了基于CAS(Compare And Set)操作的方式来实现加锁和解锁的过程。在 lock() 方法中,线程会不断地循环尝试获取锁,直到成功获取为止。而在 unlock() 方法中,线程会将锁的状态标志位设置为false,表示释放锁。


自旋锁适用于锁的持有时间较短、线程竞争不激烈的情况下,可以有效减少线程阻塞和切换带来的开销,提高系统的性能和吞吐量。


synchronized 关键字的应用场景: synchronized 关键字适用于绝大多数的同步场景,尤其是对于简单的同步操作,synchronized 提供了更为简单和便捷的使用方式。


synchronized 关键字是 Java 中最常用的同步机制,它可以用于任何对象和方法,提供了一种简单而强大的同步方式。


示例代码:

/**
 * synchronized 关键字的应用场景:
 * synchronized 关键字适用于绝大多数的同步场景,尤其是对于简单的同步操作,synchronized 提供了更为简单和便捷的使用方式。
 * synchronized 关键字是 Java 中最常用的同步机制,它可以用于任何对象和方法,提供了一种简单而强大的同步方式。
 */

public class SynchronizedCounter {
    // 定义一个计数器变量
    private int count = 0;

    /**
     * synchronized 关键字修饰的方法,用于增加计数器的值
     */
    public synchronized void increment() {
        // 在方法内部使用 synchronized 关键字,确保了对 count 变量的操作是原子的,即线程安全的
        count++;
    }

    /**
     * synchronized 关键字修饰的方法,用于获取计数器的值
     */
    public synchronized int getCount() {
        // 在方法内部使用 synchronized 关键字,确保了对 count 变量的读取操作是线程安全的
        return count;
    }
}

在上述代码中,我定义了一个简单的计数器类 SynchronizedCounter,其中的 increment() 和 getCount() 方法都使用了 synchronized 关键字修饰,这样可以确保对计数器变量 count 的操作是线程安全的。无论是增加计数器的值还是获取计数器的值,都能保证在多线程环境下的正确性和一致性。


synchronized 关键字适用于大多数的同步场景,特别是对于简单的同步操作,它提供了一种简单而强大的同步方式。因此,在绝大多数情况下,我可以使用 synchronized 关键字来确保线程安全,而无需引入更复杂的同步机制。


相关文章
|
6天前
|
存储 Java 开发者
面试官:小伙子知道synchronized的优化过程吗?我:嘚吧嘚吧嘚,面试官:出去!
面试官:小伙子知道synchronized的优化过程吗?我:嘚吧嘚吧嘚,面试官:出去!
23 1
|
3月前
|
Java
【面试问题】Synchronized 和 ReentrantLock 区别?
【1月更文挑战第27天】【面试问题】Synchronized 和 ReentrantLock 区别?
|
1月前
|
存储 关系型数据库 MySQL
最全MySQL面试60题(含答案):存储引擎+数据库锁+索引+SQL优化等
最全MySQL面试60题(含答案):存储引擎+数据库锁+索引+SQL优化等
193 0
|
2月前
|
机器学习/深度学习 算法 搜索推荐
|
3月前
|
算法 Java
【面试问题】锁如何优化?
【1月更文挑战第27天】【面试问题】锁如何优化?
|
3月前
|
监控 Java
【面试问题】什么是锁的自适应自旋?
【1月更文挑战第27天】【面试问题】什么是锁的自适应自旋?
|
3月前
|
设计模式 消息中间件 Java
面试官:什么是JIT、逃逸分析、锁消除、栈上分配和标量替换?
面试官:什么是JIT、逃逸分析、锁消除、栈上分配和标量替换?
537 1
|
4月前
|
安全 Java API
锁策略相关问题(面试常考)
锁策略相关问题(面试常考)
43 0
锁策略相关问题(面试常考)
|
5月前
|
存储 Java
面试~Synchronized 与 锁升级
面试~Synchronized 与 锁升级
36 0
|
1月前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
62 1