AI智能
改变未来

Java并发编程之锁


一、 Lock 锁

java.util.concurrent.locks.Lock

为什么有了synchronized,还需要Lock呢?

  • 使用方式更灵活
  • 性能开销小

1.1 ReentrantLock

简单示例:

public class TestLock {private Lock lock=new ReentrantLock();private int value;public void add(){try {lock.lock();value++;} finally {lock.unlock();}}}

ReentrantLock:可重入锁
new ReentrantLock(true):可重入锁+公平锁

可重入锁:某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。 ReentrantLock和synchronized都是可重入锁。

公平锁:先排队的先获得锁。

可重入锁的示例:

public class TestLock {private Lock lock=new ReentrantLock();private int value;public  void add(){try {lock.lock();value++;//已经获取了锁,再进入也要获取锁的print方法,不会产生死锁print();} finally {lock.unlock();}}public  void print(){try {lock.lock();System.out.println(\"打印内容\");} finally {lock.unlock();}}public static void main(String[] args) {new TestLock().add();}}

1.2 ReentrantReadWriteLock 读写锁

读写锁示例:

public class TestLock {private int sum;private ReadWriteLock lock=new ReentrantReadWriteLock(true);public int incrAndGet(){try {lock.writeLock().lock();sum++;return sum;} finally {lock.writeLock().unlock();}}public int getSum(){try {lock.readLock().lock();return sum;} finally {lock.readLock().unlock();}}}

读读不互斥,读写互斥,写写互斥

适用于读多写少的场景。

读为什么要加锁呢?
答:避免读的时候去写。

1.3 Condition的用法

使用Condition实现了生产-消费的案例

public class TestLock {private int sum;Lock lock = new ReentrantLock();Condition notFull  = lock.newCondition();Condition notEmpty=lock.newCondition();public void consumer() throws InterruptedException {try {lock.lock();while (sum<=0){notEmpty.await();}sum--;System.out.println(\"消费者:\"+sum);notFull.signal();} finally {lock.unlock();}}public void producer() throws InterruptedException {try {lock.lock();wad8hile (sum>=20){notFull.await();}sum++;System.out.println(\"生产者:\"+sum);notEmpty.signal();} finally {lock.unlock();}}public static void main(String[] args) {TestLock testLock=new TestLock();new Thread(new Runnable() {@Overridepublic void run() {try {while (true){testLock.consumer();}} catch (InterruptedException e) {e.printStackTrace();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {while (true){testLock.producer();}} catch (InterruptedException e) {e.printStackTrace();}}}).start();}}

1.4 LockSupport 锁当前线程

使用示例:

public class TestThread {public static void main(String[] args) throws InterruptedException {ChildThread childThread = new ChildThread(Thread.currentThread());childThread.start();LockSupport.park();System.out.println(\"主线程结束\");}private static class ChildThread  extends Thread{private Thread thread;public ChildThread(Thread thread) {this.thread = thread;}@Overridepublic void run() {try {Thread.sleep(3000);System.out.println(\"模拟一些初始化操作\");LockSupport.unpark(thread);} catch (InterruptedException e) {e.printStackTrace();}}}}

用锁的最佳实践

  1. 永远只在更新对象的成员变量时加锁
  2. 永远只在访问可变的成员变量时加锁
  3. 永远不再调用其他对象的方法时加锁

二、并发原子类

并发原子类所在的包:java.util.concurrent.atomic

使用示例:

public class TestLock {private AtomicInteger atomicInteger=new AtomicInteger();public int add(){return atomicInteger.incrementAndGet()103c;}public int get(){return atomicInteger.get();}}

整个AtomicInteger的实现是无锁的。

实现原理:

  1. volatile保证读写操作都可见
  2. 使用CAS指令,通过自旋重试保证写入
  • CAS是基于Unsafe的API Compare-And-Swap
  • CPU 硬件指令支持 :CAS指令

LongAdder 对AtomicLong的改进:
分段思想。

三、并发工具类

我们已经有了锁、有了并发原子类,为什么我们还需要并发工具类呢?

思考以下场景:有一个方法,用10个线程来分别都执行,但是同时只能有4个在执行。用锁的方式就不好实现。所以JDK给我们提供了以下的并发工具类。

3.1 Semaphore 信号量

应用场景:同一时间控制并发线程数。

public class TestLock {//声明4个permits(许可证)的信号量Semaphore semaphore = new Semaphore(4);public static void main(String[] args) {TestLock testLock = new TestLock();for (int i = 0; i < 10; i++) {new Thread(new Runnable() {@Overridepublic void run() {try {testLock.test();} catch (InterruptedException e) {e.printStackTrace();}}}).start();}}public void test() throws InterruptedException {//拿到一个permits(许可证),也可以带参,一个线程拿两个,如:semaphore.acquire(2);semaphore.acquire();System.out.println(Thread.currentThread().getName() + \"在工作\");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();} finally {//释放一个permitssemaphore.release();}}}

3.2 CountDownLatch 闭锁

应用场景:Master线程等待所有Worker线程把任务执行完。

示例:公司有5个人,每个人都完成了手头的工作,老板才下班。

public class TestLock2 {CountDownLatch countDownLatch=new CountDownLatch(5);public static void main(String[] args) throws InterruptedException {TestLock2 testLock = new TestLock2();for (int i = 0; i < 5; i++) {new Thread(new Runnable() {@Overridepublic void run() {testLock.test();}}).start();}testLock.countDownLatch.await();System.out.println(\"大家都工作完了,老板(监工)可以下班了\");}public void test() {System.out.println(Thread.currentThread().getName() + \"在工作\");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();System.out.println(Thread.currentThread().getName() + \"工作完了\");}}}

相当于CountDownLatch有一个计数器,调用了await方法后等待在那里了,每调用countDown方法就减一,只有当计数器减为0了,await才被唤醒。

3.3 CyclicBarrier 栅栏

场景:任务执行到一定阶段,等其他任务对齐

在作用上很类似CountDownLatch,只是使用的方法不一样。

示例:

public class TestLock2 {CyclicBarrier cyclicBarrier=new CyclicBarrier(5);public static void main(String[] args) throws InterruptedException {TestLock2 testLock = new TestLock2();for (int i = 0; i < 5; i++) {new Thread(new Runnable() {@Overridepublic void run() {testLock.test();}}).start();}}publi1996c void test() {System.out.println(Thread.currentThread().getName() + \"在工作\");try {int i = new Random().nextInt(10);TimeUnit.SECONDS.sleep(i);System.out.println(Thread.currentThread().getName() + \"工作完了\");cyclicBarrier.await();System.out.println(\"大家都工作完了,老板(监工)可以下班了\");} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}}

CyclicBarrier没有countDown方法,而是都阻塞到await那里,等阻塞的个数达到,就可以放行。

3.4 AQS 同步队列器

AQS是AbstractQueuedSynchronizer的缩写,它是构建锁或其他组件的基础。如:CountDownLatch、Semaphore、ReentrantLock。

里面有两种资源共享方式:独占|共享。 独占的实现如ReentrantLock,共享的实现如 Semaphore 。子类负责实现公平锁或者非公平锁。

以ReentrantLock为例,里面有一个state变量,如果state为0,则表示未锁定,调用tryAcquire方法独占锁,并将state+1,此后其他线程来就不能占用了,放入队列里去等着。如果还是当前线程调用,那么他是可以再获得锁的,只是state还要累加。(可重入的概念)但是要注意,获得多少次就要释放多少次才行。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Java并发编程之锁