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

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文