Java多线程相关知识点汇总_java多线程相关概念-程序员宅基地

1.ThreadLocal
2.如何保证高并发场景下的线程安全?
3.JUC(java.util.concurrent)包
4.volatile
5.信号量同步
6.线程池
7.线程同步类
8.并发集合类
9.锁机制

1.ThreadLocal
ThreadLocal如何实现多线程数据隔离?
ThreadLocal内存泄漏问题?
ThreadLocal脏数据问题?

ThreadLocal主要功能:
进行对象跨层传输,使用ThreadLocal,打破层次间的约束。
线程间数据隔离。
进行事务操作,存储线程事务信息。

注意ThreadLocal并不是解决多线程下共享资源的技术

首先需要了解下四类引用:

强引用
对象有强引用指向,并且GC Roots可达,不会回收。
软引用
引用强度弱与“强引用”,用在非必须的场景,在OOM即将发生的时候,垃圾回收器会将这些软引用指向的对象放入回收范围。
弱引用
引用强度弱于前两者,弱引用指向的对象只存在弱引用这一条路径,那么在下次YGC时会被回收。YGC时间不确定,则弱引用何时被回收也不确定。
虚引用
极其虚弱的引用对象,完成定义就无法通过该引用获取对象了。一般场景很难使用到。

ThreadLocal 与Thread类图关系,自己拍的图片,大家先看一下,等有时间我自己画一下。

在这里插入图片描述
ThreadLocal有一个静态内部类ThreadLocalMap和Entry。
Thread中持有一个ThreadLocalMap属性,在ThreadLocal->createMap()赋值的。ThreadLocal与ThreadLocalMap联系的方法:get() set() remove()
Entry继承自WeaReference,Entry只有一个value成员,它的key是ThreadLocal对象。

下面是栈与内存的的角度分析Thread ThreadLocal ThreadLocalMap三者之间的关系。
在这里插入图片描述
1个Thread有且仅有1个ThreadLocalMap对象
1个Entry对象的Key弱引用指向1个ThreadLocal对象
1个ThreadLocalMap独享存储多个Entry对象
1个ThreadLocal对象可以被多个线程共享
ThreadLocal对象不持有Value,Value由线程的Entry对象持有。

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

所有Entry对象都会被ThreadLocalMap类实例化的threadLocals持有,线程执行完毕,线程对象内的实例属性均会被垃圾回收。Entry中的Key是ThreadLocal弱引用,即使线程正在运行过程中,只有ThreadLocal对象引用被置为null,Entry的Key就会自动在下次YGC时被垃圾回收。而ThreadLocal使用set() get()时会自动将key==null的value置为null,使value也能被垃圾回收。
ThreadLocal内存泄漏问题:

在这里插入图片描述
Entry中的Key-ThreadLocal对象失去引用后,触发弱引用机制来回收Entry中的Value是不可能的,在上面的图中,ThreadLocal被回收了,但是如果没有显式的回调remove(),这时候Entry中的Value是不会被立即回收的,可能会造成内存泄漏。

线程池操作可能产生的ThreadLocal脏数据

2.如何保证高并发场景下的线程安全?
2.1 数据单线程可见
2.2 只读对象
final修饰的变量
2.3 线程安全类
StringBuffer,以及采用synchronized修饰的其他类。
2.4 同步锁工作机制

3.JUC(java.util.concurrent)包
3.1 线程同步类

object.wait()/Object.notify()
CountDownLatch
Semaphore
CyclicBarrier

3.2 并发集合类
ConcurrentHashMap
ConcurrentSkipListMap
CopyOnWriteArrayList
BlockingQueue

3.3 线程管理类
ThreadLocal
Executors静态工厂
ThreadPoolExecutor
ScheduledExecutorService

3.4 锁相关类
ReentrantLock

4.volatile

volatile解决的是多线程共享变量的可见性问题,但是不具备synchronized的互斥性,所以对volatile变量的操作并非都具有原子性。

//Author:[email protected]

public class VolatileAtomic {

private static volatile long count = 0L; private static final int
NUMBER = 10000;

public static void main(String[] args) {
Thread subtraceThread = new SubtraceThread();
subtraceThread.start();
for(int i=0;i<NUMBER;i++) {
count++;
}
while(subtraceThread.isAlive())
{

}
System.out.println("count = " + count);

}

private static class SubtraceThread extends Thread {

@Override
public void run(){
  for(int i=0;i<NUMBER;i++) {
    count--;
  }
}

} }

在这里插入图片描述

这个结果不为0就说明volatile变量是不具备synchronized的互斥性的,这很简单,因为count++与count–在底层是分为三步操作的,并不是原子操作。所以如果想得到结果为0,只需要在count++与count–上加锁即可。

volatile非常适合“一写多读”的场景,“一写多读”的经典场景是CopyOnWriteArrayList
JVM的happens-before原则:

程序次序原则:一个线程内,代码按照编写时的顺序执行。
锁住规则:unlock操作优先于lock操作
volatile变量规则:如果一个变量使用volatile关键字来修饰,一个线程对它进行读操作,另一个线程对它进行写操作,那么写操作肯定要优先于读操作。
volatile具有保证顺序性的语义。

volatile与synchronized的区别:
1.使用上区别

1.1 volatile关键字只能修饰实例变量和类变量,不能修饰方法以及方法参数、局部变量、常量等。
1.2 synchronized关键字不能修饰变量,只能修饰方法和代码块。
1.3 volatile修饰的变量可以为null,但是synchronized修饰的monitor不能为null

2.对原子性的保证

2.1 volatile无法保证原子性
2.2 synchronized 执行中无法被打断,可以保证代码的原子性。

3.对可见性的保证

3.1 两者均可以保证共享资源在多线程间的可见性,实现机制不同。
3.2 synchronized使用monitor enter与monitor exit通过排他方式使得同步代码串行化,monitor exit之后所有的共享资源都会被刷新到主内存中。
3.3 volatile使用机器指令“:lock;”方式迫使其他线程工作内存中的数据失效,不得不到主内存中再次加载。

4.对有序性的保证

4.1 volatile关键字禁止JVM编译器以及处理器对其进行重排序,可以保证有序性。
4.2 synchronized修饰的代码块或者函数中也可能发生指令重排

5.其他区别

5.1 volatile不会使线程陷入阻塞
5.2 synchronized关键字会使线程进行阻塞状态。

5.信号量同步

信号量实现多线程交替打印ABC的问题:

//[email protected]

import java.util.concurrent.Semaphore;

public class ABC_Semaphore {

public static Semaphore A = new Semaphore(1); public static
Semaphore B = new Semaphore(0); public static Semaphore C = new
Semaphore(0);

static class ThreadA extends Thread {

@Override
public void run() {
  for(int i=0;i<10;i++) {
    try {
      A.acquire();
      System.out.print('A');
      B.release();
    } catch(Exception e) {

    }
  }
}   }

static class ThreadB extends Thread {

@Override
public void run() {
  for(int i=0;i<10;i++) {
    try {
      B.acquire();
      System.out.print('B');
      C.release();
    } catch(Exception e) {

    }
  }
}   }

static class ThreadC extends Thread {

@Override
public void run() {
  for(int i=0;i<10;i++) {
    try {
      C.acquire();
      System.out.print('C');
      A.release();
    } catch(Exception e) {

    }
  }
}   }

public static void main(String[] args) {
new ThreadA().start();
new ThreadB().start();
new ThreadC().start(); } }

在这里插入图片描述
6.线程池

线程池的作用:
1.利用线程池管理并复用线程、控制最大并发数等。
2.实现任务线程队列缓存策略和拒绝机制。
3.实现某些与时间相关的功能,如定时执行、周期执行等。
4.隔离线程环境。例如交易服务和搜索服务在同一台服务器上,分别开启两个线程池,其中交易线程的消耗肯定大一点。这种将不同的服务隔离开来,便于处理较慢和较快的服务。

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

1.corePoolSize表示常驻核心线程数。如果等于0,则任务完成之后,没有任何请求进入时销毁线程池的线程;如果大于0,即使本地任务执行完毕,核心线程也不会销毁。这个值的设置非常关键,设置过大会浪费资源,设置过小会导致线程频繁地创建或销毁。

2.maximumPoolSize表示线程池能够容纳同时执行的最大线程数。如果执行的线程数大于maximumPoolSize,需要借助第五个参数来将其缓存在队列中。如果maximumPoolSize与corePoolSize相等,就是固定大小的线程池。

3.keepAliveTime表示线程池中的线程空闲时间,当空闲时间达到keepAliveTime,线程会被销毁,直到剩下corePoolSize个线程,避免浪费内存资源。当线程池的线程数大于corePoolSize时keepAliveTime才会起作用。当ThreadPoolExecutor的allowCoreThreadTimeOut变成设为true,核心线程超时也会被回收。

4.unit表示时间单位。
5.workQueue表示缓存队列。当请求的线程数大于maximumPoolSize,线程进入BlockingQueue阻塞队列。

6.threadFactory表示线程工厂。它用来生产一组相同任务的线程。
7.handler表示执行拒绝策略的对象。当超过第5个参数workQueue的任务缓存区上限的时候,可以通过该策略处理请求,这是简单的限流保护。

线程池相关的类图:
在这里插入图片描述
7.线程同步类
在这里插入图片描述
AQS是一个抽象类,主是是以继承的方式使用。AQS本身是没有实现任何同步接口的,它仅仅只是定义了同步状态的获取和释放的方法来供自定义的同步组件的使用。AQS抽象类包含如下几个方法:
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。共享模式时只用 Sync Queue, 独占模式有时只用 Sync Queue, 但若涉及 Condition, 则还有 Condition Queue。在子类的 tryAcquire, tryAcquireShared 中实现公平与非公平的区分。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源volatile state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
整个 AQS 分为以下几部分:

Node 节点, 用于存放获取线程的节点, 存在于 Sync Queue, Condition Queue, 这些节点主要的区分在于 waitStatus 的值(下面会详细叙述)

Condition Queue, 这个队列是用于独占模式中, 只有用到 Condition.awaitXX 时才会将 node加到 tail 上(PS: 在使用 Condition的前提是已经获取 Lock)

Sync Queue, 独占 共享的模式中均会使用到的存放 Node 的 CLH queue(主要特点是, 队列中总有一个 dummy 节点, 后继节点获取锁的条件由前继节点决定, 前继节点在释放 lock 时会唤醒sleep中的后继节点)

ConditionObject, 用于独占的模式, 主要是线程释放lock, 加入 Condition Queue, 并进行相应的 signal 操作。

独占的获取lock (acquire, release), 例如 ReentrantLock。

共享的获取lock (acquireShared, releaseShared), 例如 ReeantrantReadWriteLock, Semaphore, CountDownLatch

参考这篇文章的应用 Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

经典的面试题:多线程交替打印ABC的多种实现方法
7.1 CountDownLatch
CountDownLatch初始时定义了资源总量 state=count,执行countDown()不断-1,直到为0,当state=0时时才能获取锁。释放后state就一直为0。CountDownLatch是一次性的,用完之后如果再想使用就重新创建一个。

7.2 Semaphore
见《5.信号量同步》
Semaphore定义了资源总量state=permits,当state>0可以获取锁,将state减1,当state=0时只能等待其他线程释放锁,当释放锁时state加1,其他等待的线程又能获得这个锁。当permits定义为1时,就是互斥锁,当permits>1时就是共享锁。

7.3 CyclicBarrier

CyclicBarrier是基于ReentrantLock实现的,它比CountDownLatch优越的地方是可以循环使用。
8.并发集合类

这在Java底层数据结构中总结这一块内容。
8.1 ConcurrentHashMap
8.2 ConcurrentSkipListMap
8.3 CopyOnWriteArrayList
8.4 BlockingQueue
9.锁机制
9.1 ReentrantLock
要想支持重入性,就要解决两个问题:1.在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。我们知道,同步组件主要是通过重写AQS的几个protected方法来表达自己的同步语义。
公平锁 VS 非公平锁

公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

9.2 ReentrantLockReadWriteLock
ReadWriteLock: 允许读操作并发执行;不允许“读/写”, “写/写”并发执行。当数据结构需要频繁的读时,ReadWriteLock相比ReentrantLock与synchronized的性能更好。
9.3 StampedLock
读不阻塞写的实现思路:
在读的时候如果发生了写,则应当重读而不是在读的时候直接阻塞写!
因为在读线程非常多而写线程比较少的情况下,写线程可能发生饥饿现象,也就是因为大量的读线程存在并且读线程都阻塞写线程,

因此写线程可能几乎很少被调度成功!当读执行的时候另一个线程执行了写,则读线程发现数据不一致则执行重读即可。所以读写都存在的情况下,

使用StampedLock就可以实现一种无障碍操作,即读写之间不会阻塞对方,但是写和写之间还是阻塞的!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhuguang10/article/details/88670537

智能推荐

ROS moveit 机械臂避障运动规划_moveit运动过程中规避干涉-程序员宅基地

文章浏览阅读6.6k次,点赞6次,收藏104次。机械臂moveit编程(python)moveit默认使用的运动规划库OMPL支持臂章规划,这里选用RRT算法,使用move group中的PlanningSceneInterface()添加障碍物,观察机械臂运动效果。程序流程:1.初始化需要控制的规划组,初始化场景;2.设置运动约束(可选);3.设置终端link;4.清理上一次运行的残留物体5.设置障碍物size和位姿,并使用sc..._moveit运动过程中规避干涉

java poi-tl处理world动态表格_dynamictablerenderpolicy+-程序员宅基地

文章浏览阅读1w次,点赞4次,收藏12次。简单的模板处理可以参考以下链接:https://blog.csdn.net/liushimiao0104/article/details/78520120我用的是一下版本 <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</..._dynamictablerenderpolicy+

【keras-DeepLearning_Models】_obtain_input_shape() got an unexpected keyword argument 'include_top'_shape为什么报错-程序员宅基地

文章浏览阅读1w次,点赞7次,收藏11次。最近想跑一些主流的网络感受感受。从github上找到了 deep-learning-models 提供的几个模型,包括:inception-v2, inception-v3, resnet50, vgg16, vgg19 等等。这些代码都是基于 keras 框架,正好我最近有在学 tensorflow 和 keras,所以很想跑跑这些代码。心动不如行动,准备工作都做得差不多了,准备开始跑代码。此时,出现了一些常见的问题,也正好借此机会整理下来。_shape为什么报错

windowsXP下安装Qt4.8_xp的系统装qt-程序员宅基地

文章浏览阅读1.5k次。参考1:http://blog.csdn.net/areswhy/article/details/8697527参考2:http://www.cnblogs.com/emouse/archive/2013/01/29/2881721.html(windows下Qt安装部分)前记:一两个月以后就要找工作了,本来想学学embedded-linux Qt,无奈电脑老旧,用的虚拟机装的ubuntu_xp的系统装qt

KNN Python-Iris Data Set (鸢尾属植物数据集)_iris_data.mat-程序员宅基地

文章浏览阅读2.2k次。Iris数据集概况Iris Data Set(鸢尾属植物数据集)是我现在接触到的历史最悠久的数据集,它首次出现在著名的英国统计学家和生物学家Ronald Fisher 1936年的论文《The use of multiple measurements in taxonomic problems》中,被用来介绍线性判别式分析。在这个数据集中,包括了三类不同的鸢尾属植物:Iris Setosa,I..._iris_data.mat

解决React在安装antd之后出现的Can't resolve './locale'或者浏览器显示Cannot find module './locale’问题的两种方案_react脚手架 can't resolve './const-程序员宅基地

文章浏览阅读1.1w次,点赞4次,收藏3次。React在安装antd之后出现的Can’t resolve './locale’或者浏览器显示Cannot find module './locale’问题,是因为moment的版本有问题,而react默认使用了最新的moment,但是在[email protected]中是没有问题的。1.第一种解决方法解决方案就是配置webpack的alias,将所有的 moment 路径引用导入到 [email protected]操作步骤安装moment 依赖 npm install [email protected]_react脚手架 can't resolve './const

随便推点

成功解决numpy.core._internal.AxisError: axis -1 is out of bounds for array of dimension 0_numpy.axiserror: axis 1 is out of bounds for array-程序员宅基地

文章浏览阅读3.2w次,点赞4次,收藏16次。成功解决numpy.core._internal.AxisError: axis -1 is out of bounds for array of dimension 0目录解决问题解决思路解决方法解决问题numpy.core._internal.AxisError: axis -1 is out of bounds f..._numpy.axiserror: axis 1 is out of bounds for array of dimension 0

双电阻差分电流采样_利用采样保持放大器和RF ADC从根本上扩展带宽以突破X波段频率...-程序员宅基地

文章浏览阅读431次。摘要模拟带宽的重要性高于其他一切在越来越多的应用中得到体现。随着GSPS或RF ADC的出现,奈奎斯特域在短短几年内增长了10倍,达到多GHz范围。这帮助上述应用进一步拓宽了视野,但为了达到X波段(12 GHz频率),仍然需要更多带宽。在信号链中运用采样保持放大器 (THA),可以从根本上扩展带宽,使其远远超出ADC采样带宽,满足苛刻高带宽的应用的需求。本文将证明,针对RF市场开发的最新..._分流器采样差分电路

zencart1.55stripe信用卡内嵌支付获取卡号_zencart 回调方法checkout_process 提交参数-程序员宅基地

文章浏览阅读7.4k次。zencart1.55内嵌支付获取卡号_zencart 回调方法checkout_process 提交参数

pl/sql---约束_plsql里面约束在哪显示-程序员宅基地

文章浏览阅读474次。多表连接一.什么是约束二,注意三,表级约束和列级约束命令行:四,定义约束五,unique约束命令语句:六,主键约束命令语句01:命令语句02:七,foreign key 约束命令语句:七,foreign key 约束的关键字八,check 约束命令行:..._plsql里面约束在哪显示

javax.net.ssl.SSLException: Received fatal alert: internal_error 解决-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏2次。javax.net.ssl.SSLException: Received fatal alert: internal_error_javax.net.ssl.sslexception: received fatal alert: internal_error

Oracle & 神通数据库 清空所有表数据操作_神通数据库清除数据的脚本-程序员宅基地

文章浏览阅读1.3k次,点赞2次,收藏2次。1、拼接处truncate所有表的语句select 'truncate table '||table_name||';' from user_tables;2、Ctrl+A全选,粘贴至命令行,执行Over._神通数据库清除数据的脚本

推荐文章

热门文章

相关标签