22
33## Thread 和 Runnable
44
5- 所有的现代操作系统都通过进程和线程来支持并发。进程是通常彼此独立运行的程序的实例,比如,如果你启动了一个Java程序,操作系统产生一个新的进程,与其他程序一起并行执行。在这些进程的内部,我们使用线程并发执行代码,因此,我们可以最大限度的利用CPU可用的核心(core)。
5+ 所有的现代操作系统都通过进程和线程来支持并发。进程是通常彼此独立运行的程序的实例,比如,如果你启动了一个Java程序,操作系统产生一个新的进程,与其他程序一起并行执行。
6+ 在这些进程的内部,我们使用线程并发执行代码,因此,我们可以最大限度的利用CPU可用的核心(core)。
67
7- Java从JDK1.0开始执行线程。在开始一个新的线程之前,你必须指定由这个线程执行的代码,通常称为task。这可以通过实现Runnable——一个定义了一个无返回值无参数的run() 方法的函数接口。
8+ Java从JDK1.0开始执行线程。在开始一个新的线程之前,你必须指定由这个线程执行的代码,通常称为task。这可以通过实现Runnable——一个定义了一个无返回值无参数的 ` run() ` 方法的函数接口。
89
910
1011## 线程池
1112
12- 在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地利用线程池,线程池的优势很明显,如下:
13+ 在执行一个异步任务或并发任务时,往往是通过直接 ` new Thread()` 方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地利用线程池,线程池的优势很明显,如下:
1314
14151 . 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
15162 . 提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
@@ -20,7 +21,7 @@ Java从JDK1.0开始执行线程。在开始一个新的线程之前,你必须
2021
2122** newCachedThreadPool**
2223
23- 创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer .MAX_VALUE,可看做是无限大。
24+ 创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为 ` Integer .MAX_VALUE` ,可看做是无限大。
2425
2526``` java
2627public void cachedThreadPoolDemo(){
@@ -171,10 +172,10 @@ pool-1-thread-2, every 3s
171172...
172173```
173174
174- - schedule(Runnable command, long delay, TimeUnit unit),延迟一定时间后执行Runnable任务 ;
175- - schedule(Callable callable, long delay, TimeUnit unit),延迟一定时间后执行Callable任务 ;
176- - scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit), 延迟一定时间后,以间隔period时间的频率周期性地执行任务;
177- - scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit),与scheduleAtFixedRate() 方法很类似,
175+ - ` schedule(Runnable command, long delay, TimeUnit unit) ` : 延迟一定时间后执行 ` Runnable ` 任务 ;
176+ - ` schedule(Callable callable, long delay, TimeUnit unit) ` : 延迟一定时间后执行 ` Callable ` 任务 ;
177+ - ` scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) ` : 延迟一定时间后,以间隔period时间的频率周期性地执行任务;
178+ - ` scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit) ` : 与 ` scheduleAtFixedRate() ` 方法很类似,
178179但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,
179180而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。
180181
@@ -183,16 +184,19 @@ pool-1-thread-2, every 3s
183184** 方法对比**
184185
185186| 工厂方法 | corePoolSize | maximumPoolSize | keepAliveTime | workQueue |
186- | newCachedThreadPool | 0 | Integer.MAX_VALUE | 60s SynchronousQueue |
187- | newFixedThreadPool | nThreads | nThreads | 0 | LinkedBlockingQueue |
188- | newSingleThreadExecutor | 1 | 1 | 0 | LinkedBlockingQueue |
189- | newScheduledThreadPool | corePoolSize | Integer.MAX_VALUE | 0 | DelayedWorkQueue |
187+ | :-----:| :--------| :-------| :-------| :-------|
188+ | ` newCachedThreadPool ` | 0 | Integer.MAX_VALUE | 60s ` SynchronousQueue ` |
189+ | ` newFixedThreadPool ` | nThreads | nThreads | 0 | ` LinkedBlockingQueue ` |
190+ | ` newSingleThreadExecutor ` | 1 | 1 | 0 | ` LinkedBlockingQueue ` |
191+ | ` newScheduledThreadPool ` | corePoolSize | Integer.MAX_VALUE | 0 | ` DelayedWorkQueue ` |
190192
191193其他参数都相同,其中线程工厂的默认类为 ` DefaultThreadFactory ` ,线程饱和的默认策略为 ` ThreadPoolExecutor.AbortPolicy ` 。
192194
193195## 简单使用 Lock 锁
194196
195- Java 5 中引入了新的锁机制——java.util.concurrent.locks 中的显式的互斥锁:Lock 接口,它提供了比synchronized 更加广泛的锁定操作。Lock 接口有 3 个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。lock 必须被显式地创建、锁定和释放,为了可以使用更多的功能,一般用 ReentrantLock 为其实例化。为了保证锁最终一定会被释放(可能会有异常发生),要把互斥区放在 try 语句块内,并在 finally 语句块中释放锁,尤其当有 return 语句时,return 语句必须放在 try 字句中,以确保 unlock()不会过早发生,从而将数据暴露给第二个任务。因此,采用 lock 加锁和释放锁的一般形式如下:
197+ Java 5 中引入了新的锁机制——java.util.concurrent.locks 中的显式的互斥锁:Lock 接口,它提供了比 ` synchronized ` 更加广泛的锁定操作。
198+ Lock 接口有 3 个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。
199+ lock 必须被显式地创建、锁定和释放,为了可以使用更多的功能,一般用 ReentrantLock 为其实例化。为了保证锁最终一定会被释放(可能会有异常发生),要把互斥区放在 try 语句块内,并在 finally 语句块中释放锁,尤其当有 return 语句时,return 语句必须放在 try 字句中,以确保 unlock()不会过早发生,从而将数据暴露给第二个任务。因此,采用 lock 加锁和释放锁的一般形式如下:
196200
197201``` java
198202// 默认使用非公平锁,如果要使用公平锁,需要传入参数true
@@ -208,6 +212,48 @@ try {
208212}
209213```
210214
215+ 可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。
216+ 在JAVA环境下 ` ReentrantLock ` 和 ` synchronized ` 都是可重入锁。
217+
218+ ** ReentrantReadWriteLock**
219+
220+ 读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。
221+ 如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。
222+ 总之,读的时候上读锁,写的时候上写锁!
223+
224+ ` ReentrantReadWriteLock ` 会使用两把锁来解决问题,一个读锁,一个写锁
225+
226+ - 线程进入读锁的前提条件
227+ - 没有其他线程的写锁
228+ - 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
229+ - 线程进入写锁的前提条件
230+ - 没有其他线程的读锁
231+ - 没有其他线程的写锁
232+
233+ ## StampedLock
234+
235+ ` StampedLock ` 是 java 8 在 ` java.util.concurrent.locks ` 新增的一个API。
236+
237+ ` ReentrantReadWriteLock ` 在沒有任何读锁和写锁时,才可以取得写入锁,这可用于实现了悲观读取。
238+ 然而,如果读取很多,写入很少的情况下,使用 ` ReentrantReadWriteLock ` 可能会使写入线程遭遇饥饿问题,也就是写入线程无法竞争到锁定而一直处于等待状态。
239+ ` StampedLock ` 有三种模式的锁,用于控制读取/写入访问,StampedLock 的状态由版本和模式组成。
240+ 锁获取操作返回一个用于展示和访问锁状态的票据(stamp)变量,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。
241+ 在读锁上分为悲观锁和乐观锁,锁释放以及其他相关方法需要使用邮戳(stamps)变量作为参数,如果他们和当前锁状态不符则失败,这三种模式为:
242+
243+ - 写入:方法writeLock可能为了获取独占访问而阻塞当前线程,返回一个stamp变量,能够在unlockWrite方法中使用从而释放锁。也提供了tryWriteLock。
244+ 当锁被写模式所占有,没有读或者乐观的读操作能够成功。
245+ - 读取:方法readLock可能为了获取非独占访问而阻塞当前线程,返回一个stamp变量,能够在unlockRead方法中用于释放锁。也提供了tryReadLock。
246+ - 乐观读取:方法 ` tryOptimisticRead ` 返回一个非 0 邮戳变量,仅在当前锁没有以写入模式被持有。如果在获得stamp变量之后没有被写模式持有,方法validate将返回true。
247+ 这种模式可以被看做一种弱版本的读锁,可以被一个写入者在任何时间打断。乐观读取模式仅用于短时间读取操作时经常能够降低竞争和提高吞吐量。
248+
249+ > 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
250+ > 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
251+ > Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。
252+
253+ > 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。
254+ 乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
255+ > 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
256+
211257## AtomicInteger
212258
213259JDK1.5之后的java.util.concurrent.atomic包里,多了一批原子处理类。
@@ -219,11 +265,11 @@ AtomicInteger,一个提供原子操作的Integer的类。
219265而AtomicInteger则通过一种线程安全的加减操作接口。
220266
221267``` java
222- public final int get() // 获取当前的值
268+ public final int get() // 获取当前的值
223269public final int getAndSet(int newValue)// 获取当前的值,并设置新的值
224- public final int getAndIncrement()// 获取当前的值,并自增
225- public final int getAndDecrement() // 获取当前的值,并自减
226- public final int getAndAdd(int delta) // 获取当前的值,并加上预期的值
270+ public final int getAndIncrement() // 获取当前的值,并自增
271+ public final int getAndDecrement() // 获取当前的值,并自减
272+ public final int getAndAdd(int delta) // 获取当前的值,并加上预期的值
227273```
228274
229275## LongAccumulator
@@ -235,16 +281,6 @@ AtomicLong也可以用于这种场景,但在线程竞争激烈的情况下,L
235281` LongAccumulator ` 和 ` LongAdder ` 类似,也基于Striped64实现。但要比LongAdder更加灵活(要传入一个函数接口),
236282LongAdder相当于是LongAccumulator的一种特例。
237283
238- ## StampedLock
239-
240- StampedLock是java8在java.util.concurrent.locks新增的一个API。
241- ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取。然而,如果读取很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿问题,也就是写入线程无法竞争到锁定而一直处于等待状态。
242- StampedLock有三种模式的锁,用于控制读取/写入访问。StampedLock的状态由版本和模式组成。锁获取操作返回一个用于展示和访问锁状态的票据(stamp)变量,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。锁释放以及其他相关方法需要使用邮编(stamps)变量作为参数,如果他们和当前锁状态不符则失败,这三种模式为:
243-
244- - 写入:方法writeLock可能为了获取独占访问而阻塞当前线程,返回一个stamp变量,能够在unlockWrite方法中使用从而释放锁。也提供了tryWriteLock。当锁被写模式所占有,没有读或者乐观的读操作能够成功。
245- - 读取:方法readLock可能为了获取非独占访问而阻塞当前线程,返回一个stamp变量,能够在unlockRead方法中用于释放锁。也提供了tryReadLock。
246- - 乐观读取:方法tryOptimisticRead返回一个非0邮编变量,仅在当前锁没有以写入模式被持有。如果在获得stamp变量之后没有被写模式持有,方法validate将返回true。这种模式可以被看做一种弱版本的读锁,可以被一个写入者在任何时间打断。乐观读取模式仅用于短时间读取操作时经常能够降低竞争和提高吞吐量。
247-
248284## Semaphore
249285
250286Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
0 commit comments