全面解析各类锁及适用情形 在Java编程中,锁机制是多线程编程里极为重要的一部分,它能够帮助我们解决多线程并发访问共享资源时产生的各种问题。下面就来详细介绍Java锁机制的分类以及它们各自的使用场景。 乐观锁与悲观锁 乐观锁和悲观锁是从锁的设计理念来进行分类的。悲观锁比较悲观,它觉得在对共享资源进行访问时,一定会有其他线程来修改这个资源,所以在访问之前就会先把资源锁住,避免其他线程的干扰。Java中的synchronized和ReentrantLock都属于悲观锁。 以synchronized为例,下面是一个简单的使用案例: java public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } public static void main(String[] args) throws InterruptedException { SynchronizedExample example = new SynchronizedExample(); Thread t1 = new Thread(() -> { for (int i = 0; i example.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i example.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(example.count); } }
在这个例子中,increment方法使用了synchronized关键字,保证了同一时间只有一个线程能够执行该方法,避免了多线程并发修改count变量时出现的数据不一致问题。 而乐观锁则比较乐观,它认为在访问共享资源时,一般不会有其他线程来修改这个资源,所以不会在访问之前加锁。它是通过版本号或者CAS(Compare And Swap)算法来实现的。在Java中,AtomicInteger类就是使用乐观锁的典型代表。 下面是AtomicInteger的使用案例: java import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerExample { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { AtomicIntegerExample example = new AtomicIntegerExample(); Thread t1 = new Thread(() -> { for (int i = 0; i example.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i example.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(example.count.get()); } }
在这个例子中,AtomicInteger的incrementAndGet方法使用了CAS算法,在更新值时会先比较当前值是否和预期值一致,如果一致就更新,否则就重试,从而保证了多线程环境下数据的一致性。 公平锁与非公平锁 公平锁和非公平锁是根据线程获取锁的公平性来分类的。公平锁会按照线程请求锁的顺序来分配锁,先请求的线程先获得锁,就像排队一样。而ReentrantLock就可以通过构造函数来指定是否为公平锁。 下面是一个公平锁的使用案例: java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class FairLockExample { private Lock lock = new ReentrantLock(true); public void doSomething() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " 获得锁"); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { FairLockExample example = new FairLockExample(); for (int i = 0; i new Thread(() -> example.doSomething()).start(); } } }
在这个例子中,ReentrantLock的构造函数传入了true,表示使用公平锁。线程会按照请求锁的顺序依次获得锁。 非公平锁则不会按照请求顺序来分配锁,线程在请求锁时,会先尝试直接获取锁,如果获取不到才会进入队列等待。非公平锁的性能一般比公平锁要好,因为它减少了线程上下文切换的开销。 下面是一个非公平锁的使用案例: java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class NonFairLockExample { private Lock lock = new ReentrantLock(false); public void doSomething() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " 获得锁"); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { NonFairLockExample example = new NonFairLockExample(); for (int i = 0; i new Thread(() -> example.doSomething()).start(); } }
在这个例子中,ReentrantLock的构造函数传入了false,表示使用非公平锁。线程可能不会按照请求顺序获得锁。 可重入锁与不可重入锁 可重入锁是指同一个线程在已经获得锁的情况下,可以再次获得该锁,不会出现死锁的情况。Java中的synchronized和ReentrantLock都是可重入锁。 下面是一个synchronized可重入的案例: java public class ReentrantSynchronizedExample { public synchronized void method1() { System.out.println("进入 method1"); method2(); System.out.println("离开 method1"); } public synchronized void method2() { System.out.println("进入 method2"); } public static void main(String[] args) { ReentrantSynchronizedExample example = new ReentrantSynchronizedExample(); example.method1(); } }
在这个例子中,线程在执行method1方法时已经获得了对象的锁,在调用method2方法时可以再次获得该锁,因为synchronized是可重入锁。 不可重入锁则相反,同一个线程在已经获得锁的情况下,再次请求该锁时会被阻塞。在Java中没有直接提供不可重入锁的实现,不过我们可以自己实现一个简单的不可重入锁。 java public class NonReentrantLock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while (isLocked) { wait(); } isLocked = true; } public synchronized void unlock() { isLocked = false; notify(); } public static void main(String[] args) { NonReentrantLock lock = new NonReentrantLock(); try { lock.lock(); // 这里再次请求锁会被阻塞 // lock.lock(); lock.unlock(); } catch (InterruptedException e) { e.printStackTrace(); } } }
在这个例子中,自己实现的www.ysdslt.com/NonReentrantLock就是一个不可重入锁,当线程已经获得锁后再次请求锁时会被阻塞。 共享锁与排他锁 共享锁是指多个线程可以同时获得该锁,用于对共享资源的读操作。而排他锁则是指同一时间只有一个线程可以获得该锁,用于对共享资源的写操作。Java中的ReentrantReadWriteLock就实现了共享锁和排他锁。 下面是一个ReentrantReadWriteLock的使用案例: java import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); private ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock(); private int data = 0; public void readData() { readLock.lock(); try { System.out.println(Thread.currentThread().getName() + " 读取数据: " + data); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); } } public void writeData() { writeLock.lock(); try { data++; System.out.println(Thread.currentThread().getName() + " 写入数据: " + data); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock.unlock(); } } public static void main(String[] args) { ReadWriteLockExample example = new ReadWriteLockExample(); for (int i = 0; i new Thread(() -> example.readData()).start(); } for (int i = 0; i new Thread(() -> example.writeData()).start(); } } }
在这个例子中,多个线程可以同时调用readData方法,因为读锁是共享锁。而writeData方法同一时间只能有一个线程调用,因为写锁是排他锁。 自旋锁 自旋锁是指当一个线程在请求锁时,如果锁已经被其他线程持有,该线程不会进入阻塞状态,而是会不断地循环检查锁是否被释放,直到获得锁为止。自旋锁可以减少线程上下文切换的开销,但是如果锁被持有时间较长,会浪费CPU资源。 下面是一个简单的自旋锁实现案例: java 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); } public static void main(String[] args) { SpinLock spinLock = new SpinLock(); new Thread(() -> { spinLock.lock(); try { System.out.println(Thread.currentThread().getName() + " 获得锁"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { spinLock.unlock(); } }).start(); new Thread(() -> { spinLock.lock(); try { System.out.println(Thread.currentThread().getName() + " 获得锁"); } finally { spinLock.unlock(); } }).start(); } }
在这个例子中,自旋锁使用AtomicBoolean的compareAndSet方法来实现自旋等待。当一个线程请求锁时,如果锁已经被持有,会不断循环检查锁是否被释放。 综上所述,不同类型的锁在不同的场景下有不同的优势。我们在实际编程中,需要根据具体的需求来选择合适的锁机制,以提高程序的性能和稳定性。