Java的各类锁

chatgpt/2023/9/26 14:20:16

可重入锁

递归锁,同一个线程,外层函数获得锁,内层的也获得锁。

synchronized
  public synchronized void sendSMS() {System.out.println(Thread.currentThread().getName() + "  sendSMS......");sendEmail();}public synchronized void sendEmail() {System.out.println(Thread.currentThread().getName() + "   sendEmail.....");}
ReentrantLock

lock和unlock必须成对的出现,不成对出现会使程序不能结束

  private void get() {try {lock.lock();System.out.println(Thread.currentThread().getName() + " get.....");set();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}private void set() throws Exception {try {lock.lock();System.out.println(Thread.currentThread().getName() + " set.....");} finally {lock.unlock();}

自旋锁

获得锁的线程不会立即的阻塞,而是采用循环的方式获得锁,减少了线程上下文的切换,会循环消耗CPU资源

public class SpinLockDemo {AtomicReference<Thread> atomicReference = new AtomicReference<>();public void myLock() {Thread thread = Thread.currentThread();System.out.println(thread.getName() + " come in.......");while (!atomicReference.compareAndSet(null, thread)) {}}public void myUnLock() {Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread, null);System.out.println(Thread.currentThread().getName() + " myUnlock()....");}public static void main(String[] args) {SpinLockDemo lockDemo = new SpinLockDemo();new Thread(() -> {lockDemo.myLock();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}lockDemo.myUnLock();}, "AA").start();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {lockDemo.myLock();lockDemo.myUnLock();}, "BB").start();}}

共享锁和独占锁

class MyCache {// 保证可见性private volatile Map<String, Object> map = new HashMap<>();// 保证了原子性,但是不能读// private Lock lock = new ReentrantLock();private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();public void put(String key, Object value) {readWriteLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + "   写入....");TimeUnit.SECONDS.sleep(1);map.put(key, value);System.out.println(Thread.currentThread().getName() + " finished 写入....");} catch (Exception e) {e.printStackTrace();} finally {readWriteLock.writeLock().unlock();}}public Object get(String key) {readWriteLock.readLock().lock();try {System.out.println(Thread.currentThread().getName() + " 读取.....");TimeUnit.SECONDS.sleep(1);Object o = map.get(key);System.out.println(Thread.currentThread().getName() + " finished 读取.....");return o;} catch (Exception e) {e.printStackTrace();} finally {readWriteLock.readLock().unlock();}return null;}}public class ReadWriteDemo {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i = 0; i < 5; i++) {final int tempInt = i;new Thread(() -> {myCache.put(tempInt + "", tempInt + "");}, String.valueOf(i)).start();}for (int i = 0; i < 5; i++) {final int tempInt = i;new Thread(() -> {myCache.get(tempInt + "");}, String.valueOf(i)).start();}}}
0   写入....
0 finished 写入....
1   写入....
1 finished 写入....
2   写入....
2 finished 写入....
3   写入....
3 finished 写入....
4   写入....
4 finished 写入....
0 读取.....
1 读取.....
3 读取.....
2 读取.....
4 读取.....
2 finished 读取.....
0 finished 读取.....
1 finished 读取.....
3 finished 读取.....
4 finished 读取.....

写保证顺序,但是读不保证顺序

CountDownLatch

使用原理

  1. 创建CountDownLatch并设置计数器值。
  2. 启动多线程并且调用CountDownLatch实例的countDown()方法。
  3. 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。

使用场景

一个程序中有N个任务在执行,我们可以创建值为N的CountDownLatch,当每个任务完成后,调用一下countDown()方法进行递减count值,再在主线程中使用await()方法等待任务执行完成,主线程继续执行。

public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(6);// 等待所有人离开教室案例for (int i = 1; i <= 6; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 离开教室....");// * Decrements the count of the latch, releasing all waiting threads if// * the count reaches zero.countDownLatch.countDown();}, CountryEnum.getName(i)).start();}// Causes the current thread to wait until the latch has counted down to// * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.countDownLatch.await();System.out.println(Thread.currentThread().getName() + " ********关闭");// closeDoor();}public static void closeDoor() throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 0; i < 6; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 离开教室....");countDownLatch.countDown();}, String.valueOf(i)).start();}countDownLatch.await();System.out.println(Thread.currentThread().getName() + " ********关闭");}enum CountryEnum {ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");CountryEnum(Integer code, String msg) {this.code = code;this.msg = msg;}private Integer code;private String msg;public Integer getCode() {return code;}public String getMsg() {return msg;}public static String getName(int index) {CountryEnum[] values = CountryEnum.values();for (CountryEnum value : values) {if (index == value.getCode()) {return value.getMsg();}}return null;}}}

CyclicBarrier

7个线程是随机的,当运行完毕之后,才会执行cyclicBarrier的回调函数

  public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()-> System.out.println("完成收集....."));for (int i = 0; i < 7; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 开始.....");try {cyclicBarrier.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}, String.valueOf(i)).start();}}

CyclicBarrier和CountDownLatch的区别

  • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
  • CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
  • CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。

Semaphore

一个或多个资源的互斥作用,使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。

    // 模拟三个停车位Semaphore semaphore = new Semaphore(3);for (int i = 0; i < 6; i++) {new Thread(() -> {try {semaphore.acquire();System.out.println(Thread.currentThread().getName() + "  获得车位");TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "  离开");} catch (Exception e) {e.printStackTrace();} finally {semaphore.release();}}, String.valueOf(i)).start();}

阻塞队列

BlockingQueue

抛出异常特殊值阻塞超时
插入Add(e)OfferputOffer
移除Remove()PullTakePull
检查Element()Peak--

SynchronousQueue

单个元素的队列,不消费不生产

    SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + "put aa");synchronousQueue.put("aa");System.out.println(Thread.currentThread().getName() + "put bb");synchronousQueue.put("bb");System.out.println(Thread.currentThread().getName() + "put cc");synchronousQueue.put("cc");}catch (Exception e) {e.printStackTrace();}}, "aaa").start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(5);System.out.println(synchronousQueue.take());TimeUnit.SECONDS.sleep(5);System.out.println(synchronousQueue.take());TimeUnit.SECONDS.sleep(5);System.out.println(synchronousQueue.take());}catch (Exception e) {e.printStackTrace();}}, "bbb").start();

线程通信生产者消费者

class Sharedata {private int number = 0;private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();public void increment() {lock.lock();try {// 1.判断while (number != 0) {// 等待,不生产condition.await();}// 2.干活儿number++;System.out.println(Thread.currentThread().getName() + " 生产 " + number);// 3.唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void decrement() {lock.lock();try {// 1.判断while (number == 0) {// 等待,不生产condition.await();}// 2.干活儿number--;System.out.println(Thread.currentThread().getName() + " 消费 " + number);// 3.唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}

多线程的判断用while if只适合两个线程的判断

synchronized和lock的区别

参考文章

存在层次上

synchronized: Java的关键字,在jvm层面上

Lock: 是一个接口

锁的释放

synchronized:

1、以获取锁的线程执行完同步代码,释放锁

2、线程执行发生异常,jvm会让线程释放锁

Lock: 在finally中必须释放锁,不然容易造成线程死锁

锁的获取

synchronized: 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待

Lock: 分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁)

锁的释放(死锁产生)

synchronized: 在发生异常时候会自动释放占有的锁,因此不会出现死锁

Lock: 发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生

锁的状态

synchronized: 无法判断

Lock: 可以判断

锁的类型

synchronized: 可重入 不可中断 非公平

Lock: 可重入 可判断 可公平(两者皆可)

性能

synchronized: 少量同步

Lock: 大量同步

  • Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
  • 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
  • ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

调度

synchronized: 使用Object对象本身的wait 、notify、notifyAll调度机制

Lock: 可以使用Condition进行线程之间的调度

用法

synchronized: 在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。

Lock: 一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。

底层实现

synchronized: 底层使用指令码方式来控制锁的,映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当线程执行遇到monitorenter指令时会尝试获取内置锁,如果获取锁则锁计数器+1,如果没有获取锁则阻塞;当遇到monitorexit指令时锁计数器-1,如果计数器为0则释放锁。

Lock: 底层是CAS乐观锁,依赖AbstractQueuedSynchronizer类,把所有的请求线程构成一个CLH队列。而对该队列的操作均通过Lock-Free(CAS)操作。

循环打印

a,b,c三个线程的循环调用,每个线程分别打印10次,部分面试会考到

class Shareresource {// a=1,b=2,c=3private int number = 1;private Lock lock = new ReentrantLock();private Condition c1 = lock.newCondition();private Condition c2 = lock.newCondition();private Condition c3 = lock.newCondition();public void print5() {try {lock.lock();// 判断while (number != 1) {c1.await();}for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}// 通知number = 2;c2.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void print10() {try {lock.lock();while (number != 2) {c2.await();}for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}// 通知number = 3;c3.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void print15() {try {lock.lock();while (number != 3) {c3.await();}for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}// 通知number = 1;c1.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-5313118.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

【解惑笔记】树莓派+OpenCV+YOLOv5目标检测(Pytorch框架)

【学习资料】 子豪兄的零基础树莓派教程https://github.com/TommyZihao/ZihaoTutorialOfRaspberryPi/blob/master/%E7%AC%AC2%E8%AE%B2%EF%BC%9A%E6%A0%91%E8%8E%93%E6%B4%BE%E6%96%B0%E6%89%8B%E6%97%A0%E7%97%9B%E5%BC%80%E6%9C%BA%E6%8C%87%E5%8D%97.md#%E7%83%A7%E5%BD%95…

企业品牌如何利用KOL社媒营销做推广?

随着社交媒体的快速发展&#xff0c;社媒营销也逐渐成为企业品牌推广的重要手段。其中&#xff0c;KOL&#xff08;Key Opinion Leader&#xff0c;关键意见领袖&#xff09;社媒营销已经成为越来越多企业的选择&#xff0c;其优势在于能够利用KOL的粉丝基础和影响力&#xff0…

js -- 实现根据url地址下载文件

思路: 可以使用 fetch 和 URL.createObjectURL 方法来实现根据 URL 下载文件。以下是一个示例代码:前置环节,不能存在跨域,若存在跨域,可使用 nginx 进行处理 代码: function downloadFile(url, filename) {fetch(url).then(response => response.b

这三件事没理顺,你过不了软考

下午好&#xff0c;我的网工朋友 上周软考成绩出来了&#xff0c;大家都过了没&#xff1f; 我看好多人都说早上的题目稳过&#xff0c;下午的好多都挂了。 软考每年这个通过率&#xff0c;确实是一言难尽。 到底怎么样才能过&#xff0c;自学、培训&#xff0c;各种诀窍&am…

在Vue中使用深度选择器定制Element Plus组件样式

介绍&#xff1a; 在Vue.js开发中&#xff0c;我们经常使用Element Plus作为UI组件库&#xff0c;它提供了丰富的组件供我们使用。然而&#xff0c;有时候我们希望对Element Plus的组件样式进行一些定制&#xff0c;比如调整字体大小、改变颜色等。在这篇博客中&#xff0c;我…

C/C++开发,opencv与qt结合播放视频

目录 一、qt_ui创建 1.1 ui设置 1.2 ui及代码输出保存 二、创建工程 2.1 工程目录及编译设置 2.2 源码设计 三、编译及测试 3.1 程序编译 3.2 程序运行 首先声明&#xff0c;这是一个OpenCV 3学习文档的案例&#xff0c;但是说明有些过于省略&#xff0c;只有一些简短的代码…

Linux echo命令与反引号、重定向符号以及管道符

echo echo命令echo结合反引号echo结合重定向符echo结合管道符 echo命令 Linux中的echo命令用于在终端输出指定的文本内容或变量值。 基本语法如下&#xff1a; echo [选项] [字符串]常用选项包括&#xff1a; -e&#xff1a;启用特殊字符的解析&#xff0c;例如\n表示换行符…

【openwrt】package介绍

openwrt package介绍 OpenWrt 构建系统主要围绕package的概念展开。不管是什么软件&#xff0c;几乎都对应一个package。 这几乎适用于系统中的所有内容&#xff1a;HOST工具、交叉编译工具链、Linux 内核、内核mod、根文件系统和上层的应用软件。 一个 OpenWrt package本质上…
推荐文章