Java中锁的简单使用体验
锁是控制多个线程访问共享资源的一种同步机制。
- synchronized:可以将代码块或方法设置为同步。
- ReentrantLock:提供了比synchronized更广泛的锁操作函数。
- ReadWriteLock:允许多个线程同时读共享资源,但只允许一个线程写共享资源。如ReentrantReadWriteLock。
- StampedLock:提供了乐观读锁,可以取代ReadWriteLock。
- Semaphore:信号量,可以控制同时访问的线程个数。
不同锁的应用场景:
- synchronized:适用于加锁代码块比较小的情况。
- ReentrantLock:需要更灵活的锁获取与释放、可中断锁、公平锁等高级功能时使用。
- ReadWriteLock:读多写少的场景,允许同时多个线程读。
- StampedLock:读多写少,需要乐观读锁时使用。
- Semaphore:限流,需要控制最大运行线程数时使用。
悲观锁和乐观锁是两种不同的锁机制:
悲观锁:
- 总是假设会发生冲突,因此在访问数据的时候都采取加锁的方式。这种锁机制下访问的数据都是互斥的。
- 典型的悲观锁是通过 synchronized 或者 ReentrantLock 等来实现。
- 访问时加锁,性能较差,但确保了数据访问的正确性。
乐观锁:
- 假设不会发生冲突,只在提交操作时检查是否违反数据完整性。
- 典型的乐观锁是通过版本号机制来实现。
- 更新的时候不加锁,只是检查版本号,性能较好。但数据可能会被覆盖。
- 适用于写比较少的场景下。
总结:
- 悲观锁性能差但安全,乐观锁性能好但有冲突的风险。
- 根据实际场景需要选择合适的锁机制。读多写少时建议使用乐观锁,写操作频繁时建议使用悲观锁。
使用如下:
synchronized
package com.demo1; import lombok.NonNull; import lombok.Synchronized; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.StampedLock; public class Lock implements Runnable { private final ReentrantLock reentrantLock = new ReentrantLock(); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final StampedLock stampedLock = new StampedLock(); @Override public void run() { synchronized (Lock.class){ for (int i = 1; i < 100; i++) { System.out.println(i); try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } try { Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { //构建线程池 ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 2, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(2), new ThreadFactory() { @Override public Thread newThread(@NonNull Runnable r) { Thread t = new Thread(r); t.setName("myThread"); return t; } }, new ThreadPoolExecutor.AbortPolicy()); Lock l = new Lock(); pool.submit(l); pool.submit(l); pool.shutdown(); } }
ReentrantLock:
@Override public void run() { reentrantLock.lock(); for (int i = 1; i < 100; i++) { System.out.println(i); try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } try { Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } reentrantLock.unlock(); }
ReadWriteLock:
@Override public void run() { readWriteLock.readLock().lock(); for (int i = 1; i < 100; i++) { System.out.println(i); try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } try { Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } readWriteLock.readLock().unlock(); }
当使用读锁时,会出现如: 1 1 2 2 3 3这样交替的情况,但是写锁不会出现这种情况
StampedLock:
private final StampedLock lock = new StampedLock(); // 乐观读 public void optimisticRead() { long stamp = lock.tryOptimisticRead(); // 执行读操作 if(!lock.validate(stamp)){//当出现数据不一致问题时进行升级 // 升级为悲观读锁 stamp = lock.readLock(); try { // 重做读取操作 } finally { lock.unlockRead(stamp); } } } // 悲观写 public void pessimisticWrite() { long stamp = lock.writeLock(); try { // 写操作 } finally { lock.unlockWrite(stamp); } }
Semaphore://只允许两个线程同时使用,类似c++的pv操作
private final Semaphore semaphore = new Semaphore(2); @SneakyThrows @Override public void run() { semaphore.acquire(); System.out.println("执行"); Thread.sleep(3000); semaphore.release(); }
如果有什么错误,欢迎指正