Java并发编程之锁
2021/9/13 22:04:55
本文主要是介绍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(); while (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() { @Override public void run() { try { while (true){ testLock.consumer(); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public 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; } @Override public void run() { try { Thread.sleep(3000); System.out.println("模拟一些初始化操作"); LockSupport.unpark(thread); } catch (InterruptedException e) { e.printStackTrace(); } } } }
用锁的最佳实践
- 永远只在更新对象的成员变量时加锁
- 永远只在访问可变的成员变量时加锁
- 永远不再调用其他对象的方法时加锁
二、并发原子类
并发原子类所在的包:java.util.concurrent.atomic
使用示例:
public class TestLock { private AtomicInteger atomicInteger=new AtomicInteger(); public int add(){ return atomicInteger.incrementAndGet(); } public int get(){ return atomicInteger.get(); } }
整个AtomicInteger的实现是无锁的。
实现原理:
- volatile保证读写操作都可见
- 使用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() { @Override public 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 { //释放一个permits semaphore.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() { @Override public 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() { @Override public void run() { testLock.test(); } }).start(); } } public 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还要累加。(可重入的概念)但是要注意,获得多少次就要释放多少次才行。
这篇关于Java并发编程之锁的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-28一步到位:购买适合 SEO 的域名全攻略
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign学习入门:轻松掌握微服务通信
- 2024-12-27OpenFeign学习入门:轻松掌握微服务间的HTTP请求
- 2024-12-27JDK17新特性学习入门:简洁教程带你轻松上手
- 2024-12-27JMeter传递token学习入门教程
- 2024-12-27JMeter压测学习入门指南
- 2024-12-27JWT单点登录学习入门指南
- 2024-12-27JWT单点登录原理学习入门