多线程与高并发笔记,Java开发两年

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

Lock readLock = readWriteLock.readLock();

Lock writeLock = readWriteLock.writeLock();

使用ReadWriteLock时,适用条件是同一个数据,有大量线程读取,但仅有少数线程修改。

ReadWriteLock可以保证:

  • 只允许一个线程写入(其他线程既不能写入也不能读取);
  • 没有写入时,多个线程允许同时读(提高性能)

读写分离锁可以有效地帮助减少锁竞争,以提高系统性能,读写锁读读之间不互斥,读写,写写都是互斥的

11. Semaphore

Semaphore 是一个计数信号量,必须由获取它的线程释放。常用于限制可以访问某些资源的线程数量,例如通过 Semaphore 限流。

对于Semaphore来说,它要保证的是资源的互斥而不是资源的同步,在同一时刻是无法保证同步的,但是却可以保证资源的互斥。只是限制了访问某些资源的线程数,其实并没有实现同步。

常用方法:

1、acquire(int permits)

从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。就好比是一个学生占两个窗口。这同时也对应了相应的release方法。

2、release(int permits)

释放给定数目的许可,将其返回到信号量。这个是对应于上面的方法,一个学生占几个窗口完事之后还要释放多少

3、availablePermits()

返回此信号量中当前可用的许可数。也就是返回当前还有多少个窗口可用。

4、reducePermits(int reduction)

根据指定的缩减量减小可用许可的数目。

5、hasQueuedThreads()

查询是否有线程正在等待获取资源。

6、getQueueLength()

返回正在等待获取的线程的估计数目。该值仅是估计的数字。

7、tryAcquire(int permits, long timeout, TimeUnit unit)

如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。

8、acquireUninterruptibly(int permits)

从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

12. Exchanger

用于两个工作线程之间交换数据的封装工具类,简单说就是一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据。其定义为 Exchanger 泛型类型,其中 V 表示可交换的数据类型,对外提供的接口很简单,具体如下:

Exchanger():无参构造方法。

V exchange(V v):等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。

V exchange(V v, long timeout, TimeUnit unit):等待另一个线程到达此交换点(除非当前线程被中断或超出了指定的等待时间),然后将给定的对象传送给该线程,并接收该线程的对象。

13. LockSupport

LockSupport 是一个非常方便实用的线程阻塞工具,他可以在任意位置让线程阻塞。

LockSupport 的静态方法 park()可以阻塞当前线程,类似的还有 parkNanos(),parkUntil()等,他们实现了一个限时的等待。

| 方法 | 描述 |

| — | — |

| void park(): | 阻塞当前线程,如果调用unpark方法或者当前线程被中断,从能从park()方法中返回 |

| void park(Object blocker) | 功能同方法1,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查; |

| void parkNanos(long nanos) | 阻塞当前线程,最长不超过nanos纳秒,增加了超时返回的特性; |

| void parkNanos(Object blocker, long nanos) | 功能同方法3,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查; |

| void parkUntil(long deadline) | 阻塞当前线程,直到deadline; |

| void parkUntil(Object blocker, long deadline) | 功能同方法5,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查; |

同样的,有阻塞的方法,当然有唤醒的方法,什么呢?unpark(Thread) 方法。该方法可以将指定线程唤醒。

需要注意的是:park 方法和 unpark 方法执行顺序不是那么的严格。比如我们在 Thread 类中提到的 suspend 方法 和resume 方法,如果顺序错误,将导致永远无法唤醒,但 park 方法和 unpark 方法则不会,因为 LockSupport 使用了类似信号量的机制。他为每一个线程准备了一个许可(默认不可用),如果许可能用,那么 park 函数会立即返回,并且消费这个许可(也就是将许可变为不可用),如果许可不可用,将会阻塞。而 unpark 方法则使得一个许可变为可用

14. AQS

AQS 为 AbstractQueuedSynchronizer 的简称

AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。

这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。

AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。

#比如

Semaphore 用它来表现剩余的许可数,

ReentrantLock 用它来表现拥有它的线程已经请求了多少次锁;

FutureTask 用它来表现任务的状态(尚未开始、运行、完成和取消)

  • 使用须知

Usage

To use this class as the basis of a synchronizer, redefine the following methods, as applicable, by inspecting and/or modifying the synchronization state using {@link #getState}, {@link #setState} and/or {@link #compareAndSetState}:

  • {@link #tryAcquire}

  • {@link #tryRelease}

  • {@link #tryAcquireShared}

  • {@link #tryReleaseShared}>

  • {@link #isHeldExclusively}

以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法:

支持独占(排他)获取锁的同步器应该实现tryAcquire、 tryRelease、isHeldExclusively;

支持共享获取锁的同步器应该实现tryAcquireShared、tryReleaseShared、isHeldExclusively。

  • AQS浅析

AQS的实现主要在于维护一个"volatile int state"(代表共享资源)和

一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。

队列中的每个节点是对线程的一个封装,包含线程基本信息,状态,等待的资源类型等。

#state的访问方式有三种:

getState()

setState()

compareAndSetState()

#AQS定义两种资源共享方式

Exclusive(独占,只有一个线程能执行,如ReentrantLock)

Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)

不同的自定义同步器争用共享资源的方式也不同。

自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,

至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。

自定义同步器实现时主要实现以下几种方法:

isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

以ReentrantLock为例

state初始化为0,表示未锁定状态。

A线程lock()时,会调用tryAcquire()独占该锁并将state+1。

此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。

当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。

但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

以CountDownLatch以例

任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。

这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。

等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

一般来说,自定义同步器要么是独占方法,要么是共享方式,

他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。

但AQS也支持自定义同步器同时实现独占和共享两种方式,如"ReentrantReadWriteLock"。

15. 锁基本概念
  • 公平锁/非公平锁
  • 可重入锁
  • 独享锁/共享锁
  • 互斥锁/读写锁
  • 乐观锁/悲观锁
  • 分段锁
  • 偏向锁/轻量级锁/重量级锁
  • 自旋锁
  • 公平锁/非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁。

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,

有可能后申请的线程比先申请的线程优先获取锁;

有可能会造成优先级反转或者饥饿现象。

对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。

非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized而言,也是一种非公平锁。

由于其并不像ReentrantLock是通过AQS的来实现线程调度,

所以并没有任何办法使其变成公平锁。

  • 可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

ReentrantLock, Synchronized都是可重入锁。

可重入锁的一个好处是可一定程度避免死锁

  • 独享(排他)锁/共享锁

独享锁是指该锁一次只能被一个线程所持有。

共享锁是指该锁可被多个线程所持有。

对于ReentrantLock而言,其是独享锁。

但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。

读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

对于Synchronized而言,当然是独享锁。

  • 互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

互斥锁在Java中的具体实现就是ReentrantLock

读写锁在Java中的具体实现就是ReadWriteLock

  • 乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

悲观锁 (Synchronized 和 ReentrantLock)

认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。

因此对于同一个数据的并发操作,悲观锁采取加锁的形式。

悲观的认为,不加锁的并发操作一定会出问题。

乐观锁 (java.util.concurrent.atomic包)

认为对于同一个数据的并发操作,是不会发生修改的。

在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。

乐观的认为,不加锁的并发操作是没有事情的。

悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,

不加锁会带来大量的性能提升。

悲观锁在Java中的使用,就是利用各种锁。

乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法。

典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

  • 分段锁

分段锁其实是一种锁的设计,并不是具体的一种锁,ConcurrentHashMap并发的实现就是通过分段锁的形式来实现高效的并发操作。

ConcurrentHashMap中的分段锁称为Segment,

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

总结

虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。

上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料

有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。
里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-K1oWqbx0-1712080774304)]

总结

虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。

[外链图片转存中…(img-3GCB58SN-1712080774304)]

[外链图片转存中…(img-axaKARiH-1712080774304)]

上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料

有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值