多线程与高并发笔记,java图形界面编程视频

当前线程睡眠指定mills毫秒

  • Thread.sleep([mills])

当前线程优雅让出执行权

  • Thread.yield()

例如Thread t1, t2,在t2的run方法中调用t1.join(),线程t2将等待t1完成后执行

  • join

3. Thread状态

| 状态 | 使用场景 |

| — | — |

| NEW | Thread被创建之后,未start之前 |

| RUNNABLE | 在调用start()方法之后,这也是线程进入运行状态的唯一一种方式。

具体分为ready跟running,当线程被挂起或者调用Thread.yield()的时候为ready |

| WAITING | 当一个线程执行了Object.wait()的时候,它一定在等待另一个线程执行Object.notify()或者Object.notifyAll()。

或者一个线程thread,其在主线程中被执行了thread.join()的时候,主线程即会等待该线程执行完成。当一个线程执行了LockSupport.park()的时候,其在等待执行LockSupport.unpark(thread)。当该线程处于这种等待的时候,其状态即为WAITING。需要关注的是,这边的等待是没有时间限制的,当发现有这种状态的线程的时候,若其长时间处于这种状态,也需要关注下程序内部有无逻辑异常。 |

|

TIMED_WAITING

| 这个状态和WAITING状态的区别就是,这个状态的等待是有一定时效的

Thread.sleep(long)

Object.wait(long)

Thread.join(long)

LockSupport.parkNanos()

LockSupport.parkUntil()

|

| BLOCKED | 在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态 |

| TERMINATED | 线程执行结束之后的状态。

线程一旦终止了,就不能复生。

在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常 |

image

4. synchronized

  • 锁住的是对象而不是代码
  • this 等价于 当前类.class
  • 锁定方法,非锁定方法同时进行
  • 锁在执行过程中发生异常会自动释放锁
  • synchronized获得的锁是可重入的
  • 锁升级 偏向锁-自旋锁-重量级锁
  • synchronized(object)不能用String常量/Integer,Long等基本数据类型
  • 锁定对象的时候要保证对象不能被重写,最好加final定义

4. volatile

  • 保证线程可见性
  • 禁止指令重排序
  • volatile并不能保证多个线程修改的一致性,要保持一致性还是需要synchronized关键字
  • volatile 引用类型(包括数组)只能保证引用本身的可见性,不能保证内部字段的可见性
volatile关 键字只能用于变量而不可以修饰方法以及代码块

5. synchronized与AtomicLong以及LongAdder的效率对比

Synchronized 是需要加锁的,效率偏低;

AtomicLong 不需要申请锁,使用CAS机制;

LongAdder 使用分段锁,所以效率好,在并发数量特别高的时候,LongAdder最合适

6. ConcurrentHashMap的分段锁原理

分段锁就是将数据分段上锁,把锁进一步细粒度化,有助于提升并发效率。

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

7. ReentrantLock

ReentrantLock可以替代synchronized

但是ReentrantLock必须手动开启锁/关闭锁,synchronized遇到异常会自动释放锁,ReentrantLock需要手动关闭,一般都是放在finally中关闭

定义锁 Lock lock = new ReentrantLock();

开启 lock.lock();

关闭 lock.unlock();

使用Reentrantlock可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。

使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行

可以根据tryLock的返回值来判定是否锁定

也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中,如果tryLock未锁定,则不需要unlock

使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断

new ReentrantLock(true) 表示公平锁,不带参数默认为false,非公平锁

8. CountDownLatch

countDownLatch这个类可以使一个线程等待其他线程各自执行完毕后再执行。

是通过一个计数器来实现的,计数器的初始值是线程的数量。当调用countDown()方法后,每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

线程中调用countDown()方法开始计数;

在调用await()方法的线程中,当计数器为0是后续才会继续执行,否则一直等待;

也可以使用latch.await(timeout, unit)在等待timeout时间后如果计数器不为0,线程仍将继续。

countDown()之后的代码不受计数器控制

与join区别,使用join的线程将被阻塞,使用countDown的线程不受影响,只有调用await的时候才会阻塞

8. CyclicBarrier

作用就是会让指定数量的(数量由构造函数指定)所有线程都等待完成后才会继续下一步行动。

构造函数:

public CyclicBarrier(int parties)

public CyclicBarrier(int parties, Runnable barrierAction)

parties 是线程的个数;

barrierAction为最后一个到达线程要做的任务

所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。

实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。

9. Phaser

可重复使用的同步屏障,功能类似于CyclicBarrier和CountDownLatch,但支持更灵活的使用。

Phaser使我们能够建立在逻辑线程需要才去执行下一步的障碍等。

我们可以协调多个执行阶段,为每个程序阶段重用Phaser实例。每个阶段可以有不同数量的线程等待前进到另一个阶段。我们稍后会看一个使用阶段的示例。

要参与协调,线程需要使用Phaser实例 register() 本身。请注意:这只会增加注册方的数量,我们无法检查当前线程是否已注册 - 我们必须将实现子类化以支持此操作。

线程通过调用 arriAndAwaitAdvance() 来阻止它到达屏障,这是一种阻塞方法。当数量到达等于注册的数量时,程序的执行将继续,并且数量将增加。我们可以通过调用getPhase()方法获取当前数量。

10. ReadWriteLock

ReadWriteLock的具体实现是ReentrantReadWriteLock

ReadWriteLock允许分别创建读锁跟写锁

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) 方法。该方法可以将指定线程唤醒。

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

需要注意的是: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. 锁基本概念

  • 公平锁/非公平锁
  • 可重入锁

猜你喜欢

转载自blog.csdn.net/m0_65484000/article/details/122133561