1)关键字 synchronized
在 方法,代码片段都可使用,示例如下:
public static void needLockBySync() { synchronized (ReentrantLockTest.class) { try { System.out.println(Thread.currentThread().getName() + "开始工作"); TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } |
2)重入锁 new ReentrantLock
表示该锁能够支持一个线程对资源的重复加锁。示例如下:
public class ReentrantLockTest { private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException { IntStream.range(0, 2).forEach(i -> new Thread(ReentrantLockTest::needLock).start()); } public static void needLock() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "开始工作"); TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } |
**确保在finally里释放锁,否则容易早成死锁
检测当前资源是否被占用,可使用方法tryLock检测,返回类型:boolean
测试:
public class ReentrantLockTest { private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(ReentrantLockTest::testTryLock, "thread1"); thread1.start(); TimeUnit.SECONDS.sleep(1);
Thread thread2 = new Thread(ReentrantLockTest::testTryLock, "thread2"); thread2.start(); } public static void testTryLock() { if (lock.tryLock()) { try { System.out.println(Thread.currentThread().getName() + "开始工作"); //进入死循环,不释放锁 while (true) { } } finally { lock.unlock(); } } else { System.out.println(Thread.currentThread().getName() + "没有获取到锁"); } } } |
ReentrantLock一些别的方法:
方法 | 含义 |
getQueueLength() | 等待获取锁线程数量 |
hasQueuedThreads() | 是否有在等待获取锁的线程 |
hasQueuedThread(Thread thread) | 等待获取锁的线程队列里是包含指定的线程 |
isLocked | 当前锁是否被任意一个线程获取到了 |
3)读写锁 ReadWriteLock
读写锁,遵循以下规则:
- 写的时候不能读
- 写的时候不能写
- 读的时候不能写
- 读的时候可以读
读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
ReadWriteLock为接口,我们使用它的实现类ReentrantReadWriteLock创建读写锁实例:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
使用读写锁创建一个读写的例子:
public class ReadWriteLockTest { private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); // 读锁 private static ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); // 写锁 private static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); // 存放数据 private static List<Long> data = new ArrayList<>();
public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (true) { write(); } }, "writer").start(); new Thread(() -> { while (true) { read(); } }, "reader").start();
}
public static void write() { try { writeLock.lock(); // 写锁 TimeUnit.SECONDS.sleep(1); long value = System.currentTimeMillis(); data.add(value); System.out.println(Thread.currentThread().getName() + " 写入value: " + value); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock.unlock(); // 释放写锁 } }
public static void read() { try { readLock.lock(); // 获取读锁 TimeUnit.SECONDS.sleep(1); String value = data.stream().map(String::valueOf).collect(Collectors.joining(",")); System.out.println(Thread.currentThread().getName() + "读取data: " + value); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); // 释放读锁 } } } |
4)StampedLock
JDK8 新增了一个锁StampedLock,它是对ReadWriteLock的改进。
使用ReadWriteLock的时候,当读线程数量远大于写线程数量的时候就会出现“写饥饿”现象。因为锁大概率都被读线程抢走了,写线程很难抢到锁,这将使得读写效率非常低下。
JDK8的StampedLock就是为了解决这个问题而设计的,StampedLock包含乐观锁和悲观锁:
- 乐观锁:每次去拿数据的时候,并不获取锁对象,而是判断标记位(stamp)是否有被修改,如果有修改就再去读一次。
- 悲观锁:每次拿数据的时候都去获取锁。
通过乐观锁,当写线程没有写数据的时候,标志位stamp并没有改变,所以即使有再多的读线程在读取数据,它们都可以直接去读数据,而无需获取锁,这就不会使得写线程抢不到锁了。
stamp类似一个时间戳的作用,每次写的时候对其+1来改变被操作对象的stamp值。实例:
public class StampedLockTest { private static StampedLock lock = new StampedLock(); private static List<Long> data = new ArrayList<>();
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(20);
Runnable read = StampedLockTest2::read; Runnable write = StampedLockTest2::write;
IntStream.range(0, 19).forEach(i -> executorService.submit(read)); executorService.submit(write);
executorService.shutdown(); }
private static void read() { long stamped = lock.tryOptimisticRead(); // 获取乐观锁 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 直接读取值 String collect = data.stream().map(String::valueOf).collect(Collectors.joining(","));
// 如果戳被改变,方法返回false,说明stamped被修改过了(被write方法修改过了,有新的数据写入), // 那么重新获取锁并去读取值,否则直接使用上面读取的值。 if (!lock.validate(stamped)) { try { stamped = lock.readLock(); TimeUnit.SECONDS.sleep(1); collect = data.stream().map(String::valueOf).collect(Collectors.joining(",")); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlockRead(stamped); } } System.out.println(Thread.currentThread().getName() + " read value: " + collect); }
private static void write() { long stamped = -1; try { stamped = lock.writeLock(); TimeUnit.SECONDS.sleep(1); long value = System.currentTimeMillis(); data.add(value); System.out.println(Thread.currentThread().getName() + " write value: " + value); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlockWrite(stamped); } } } |