Java并发编程

2023-06-23  本文已影响0人  我要离开浪浪山

1、volatile,最轻量的同步机制

volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某
个变量的值,这新值对其他线程来说是立即可见的。

参见代码:

image.png
不加 volatile 时,子线程无法感知主线程修改了 ready 的值,从而不会退出循环,
而加了 volatile 后,子线程可以感知主线程修改了 ready 的值,迅速退出循环。
但是 volatile 不能保证数据在多个线程下同时写时的线程安全,
volatile 最适用的场景:一个线程写,多个线程读。

2、ThreadLocal 和 Synchonized的比较

ThreadLocal 和 Synchonized 都用于解决多线程并发訪问。可是 ThreadLocal
与 synchronized 有本质的差别。synchronized 是利用锁的机制,使变量或代码块
在某一时该仅仅能被一个线程訪问。而 ThreadLocal 为每个线程都提供了变量的
副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线
程对数据的数据共享。

3、ThreadLocal 的使用

ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:

• void set(Object value)

设置当前线程的线程局部变量的值。

• public Object get()

该方法返回当前线程所对应的线程局部变量。

• public void remove()

将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK
5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动
被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它
可以加快内存回收的速度。

• protected Object initialValue()

返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为
了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get()
或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一
个 null。

public final static ThreadLocal<String> RESOURCE = new
ThreadLocal<String>();RESOURCE代表一个能够存放String类型的ThreadLocal对象。
此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是
线程安全的。

4、引发的内存泄漏分析

根据我们前面对 ThreadLocal 的分析,我们可以知道每个 Thread 维护一个
ThreadLocalMap,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需
要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key
来让线程从 ThreadLocalMap 获取 value。仔细观察 ThreadLocalMap,这个 map
是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
因此使用了 ThreadLocal 后,

引用链如图所示:

image.png

图中的虚线表示弱引用。

这样,当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal
实例,所以 threadlocal 将会被 gc 回收。这样一来,ThreadLocalMap 中就会出现
key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前
线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强
引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块 value 永
远不会被访问到了,所以存在着内存泄露。

只有当前 thread 结束以后,current thread 就不会存在栈中,强引用断开,
Current Thread、Map value 将全部被 GC 回收。最好的做法是不在需要使用
ThreadLocal 变量后,都调用它的 remove()方法,清除数据

5、等待/通知机制

是指一个线程 A 调用了对象 O 的 wait()方法进入等待状态,而另一个线程 B
调用了对象 O 的 notify()或者 notifyAll()方法,线程 A 收到通知后从对象 O 的 wait()
方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象
上的 wait()和 notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通
知方之间的交互工作。

notify():
通知一个在对象上等待的线程,使其从 wait 方法返回,而返回的前提是该线程
获取到了对象的锁,没有获得锁的线程重新进入 WAITING 状态。

notifyAll():
通知所有等待在该对象上的线程

wait()
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断
才会返回.需要注意,调用 wait()方法后,会释放对象的锁

wait(long)
超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n 毫秒,如果没有
通知就超时返回

wait (long,int)
对于超时时间更细粒度的控制,可以达到纳秒

6、等待和通知的标准范式

image.png image.png

在调用 wait()、notify()系列方法之前,线程必须要获得该对象的对象级 别锁,即只能在同步方法或同步块中调用 wait()方法、notify()系列方法,进入 wait()方法后,当前线程释放锁,在从 wait()返回前,线程与其他线程竞争重新获得锁,执行 notify()系列方法的线程退出调用了 notifyAll 的 synchronized代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

7、notify 和 notifyAll 应该用谁

尽可能用 notifyall(),谨慎使用 notify(),因为 notify()只会唤醒一个线程,我
们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程。

8、调用 yield() 、sleep()、wait()、notify()等方法对锁有何影响?

9、Fork-Join 原理

image.png

10、工作密取

  • 即当前线程的 Task 已经全被执行完毕,则自动取到其他线程的 Task 池中取
    出 Task 继续执行。
  • ForkJoinPool 中维护着多个线程(一般为 CPU 核数)在不断地执行 Task,每
    个线程除了执行自己职务内的 Task 之外,还会根据自己工作线程的闲置情况去
    获取其他繁忙的工作线程的 Task,如此一来就能能够减少线程阻塞或是闲置的时
    间,提高 CPU 利用率。

11、CountDownLatch

闭锁,CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工
作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动
所有的框架服务之后再执行。

CountDownLatch 是通过一个计数器来实现的,计数器的初始值为初始任务
的数量。每当完成了一个任务后,计数器的值就会减 1
(CountDownLatch.countDown()方法)。当计数器值到达 0 时,它表示所有的已
经完成了任务,然后在闭锁上等待 CountDownLatch.await()方法的线程就可以恢
复执行任务。

image.png

12、Callable、Future和FutureTask

image.png

13、Exchange的作用、应用场景和实战

ee29dad4af4b566a5c3b35f94103ee6.png

14、Semaphore的作用、应用场景和实战

057bffb9977123820a62fd737d48a47.png
上一篇 下一篇

猜你喜欢

热点阅读