java的多线程高并发详解
作者:清云青云 发布时间:2022-06-28 10:12:06
1.JMM数据原子操作
read(读取)∶从主内存读取数据
load(载入):将主内存读取到的数据写入工作内存
use(使用):从工作内存读取数据来计算
assign(赋值):将计算好的值重新赋值到工作内存中
store(存储):将工作内存数据写入主内存
write(写入):将store过去的变量值赋值给主内存中的变量
lock(锁定):将主内存变量加锁,标识为线程独占状态
unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
2.来看volatile关键字
(1)启动两个线程
public class VolatileDemo {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!flag){
}
System.out.println("跳出while循环了");
}).start();
Thread.sleep(2000);
new Thread(() -> changeFlage()).start();
}
private static void changeFlage() {
System.out.println("开始改变flag值之前");
flag = true;
System.out.println("改变flag值之后");
}
}
没加volatile之前,第一个线程的while判断一直满足
(2)给变量flag加了volatile之后
public class VolatileDemo {
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!flag){
}
System.out.println("跳出while循环了");
}).start();
Thread.sleep(2000);
new Thread(() -> changeFlage()).start();
}
private static void changeFlage() {
System.out.println("开始改变flag值之前");
flag = true;
System.out.println("改变flag值之后");
}
}
while语句能够满足条件
(3)原理解释:
开启第一个线程时,flag变量通过read从主内存中读出数据,使用load把数据加载进线程一的工作内存,通过use把flag读取到线程中;线程二也是同样的读取操作。线程二通过assign改变了flag的值,线程二工作内存中存储的flag=true,再通过store把flag写入到总线,总线再把flag通过write写入到住内存;由于两个线程读取操作的都是各种工作内存中的值,是主内存的副本,相互不通信,所以线程一一直再循环,线程一的flag为false。
加了volatile后,添加了缓存一致性协议(MESI),CPU通过总线嗅探机制感知到数据的变化而自己缓存里的值失效,此时线程一会把工作内存中存放的flag失效,从主内存中重新读取flag的值,此时满足while条件。
volatile底层通过汇编语言的lock修饰,当变量有修改立马写回主类,避免指令重排序
3.并发编程三大特性
可见性,有序性、原子性
4.双锁判断机制创建单例模式
public class DoubleCheckLockSinglenon {
private static volatile DoubleCheckLockSinglenon doubleCheckLockSingleon = null;
public DoubleCheckLockSinglenon(){}
public static DoubleCheckLockSinglenon getInstance(){
if (null == doubleCheckLockSingleon) {
synchronized(DoubleCheckLockSinglenon.class){
if(null == doubleCheckLockSingleon){
doubleCheckLockSingleon = new DoubleCheckLockSinglenon();
}
}
}
return doubleCheckLockSingleon;
}
public static void main(String[] args) {
System.out.println(DoubleCheckLockSinglenon.getInstance());
}
}
当线程调用getInstance方法创建的时候,先判断是否为空,为空则把对象加上锁,否则多线程的情况会创建重复,再锁里面再次判断是否为空,当new一个对象的时候,先在内存分配空间,再执行对象的init属性赋零操作,再执行初始化赋值操作。
cpu为了优化代码执行效率,会对满足as-if-serial和happens-before原则的代码进行指令重排序,as-if-serial规定线程内的执行代码顺序不影响结果输出,则会进行指令重排;
happens-before规定一些锁的顺序,同一个对象的unlock需要出现下一个lock之前等。
所以为了防止new的时候,指令重排,先进行赋值再执行赋零操作情况,需要加上volatile修饰符,加上volatile修饰后,在new操作时会创建内存屏障,高速cpu不进行指令重排序,底层是lock关键字;内存屏障分为LoadLoad(读读)、storestore(写写)、loadstore(读写)、storeload(写读),底层是c++代码写的,c++代码再调用汇编语言
5.synchronized关键字
(1)没加synchronized之前
package com.qingyun;
/**
* Synchronized关键字
*/
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Num num = new Num();
Thread t1 = new Thread(() -> {
for (int i = 0;i < 100000;i++) {
num.incrent();
}
});
t1.start();
for (int i = 0;i < 100000;i++) {
num.incrent();
}
t1.join();
System.out.println(num.getNum());
}
}
package com.qingyun;
public class Num {
public int num = 0;
public void incrent() {
num++;
}
public int getNum(){
return num;
}
}
输出结果不是我们想要的,由于线程和for循环同时去调加的方法,导致最后输出的结果不是我们想要的
(2)加上synchronized之后
public synchronized void incrent() {
num++;
}
//或者
public void incrent() {
synchronized(this){
num++;
}
}
输出的结果是我们想要的,synchronized关键字底层使用的lock,是重量级锁,互斥锁、悲观锁,jdk1.6之前的锁,线程会放到一个队列里面等待着执行
6.AtomicIntger原子操作
(1)给原子加1的操作,可以使用AtomicInteger实现,与synchronized相比,性能大大提升
public class Num {
// public int num = 0;
AtomicInteger atomicInteger = new AtomicInteger();
public void incrent() {
atomicInteger.incrementAndGet(); //原子加1
}
public int getNum(){
return atomicInteger.get();
}
}
AtomicInteger源码有一个value字段,使用volatile修饰,volatile底层使用lock修饰,保证多线程并发结果的正确
private volatile int value;
(2)atomicInteger.incrementAndGet()方法做的事情:先获取到value的值,给值加1,再使用旧的值和atomicInteger进行比较,相等了把newValue设置进去,由于使用多线程可能值会不相等的情况,所以使用while进行循环比对,相等了执行完才推出
while(true) {
int oldValue = atomicInteger.get();
int newValue = oldValue+1;
if(atomicInteger.compareAndSet(oldValue,newValue)){
break;
}
}
(3)atomicInteger.compareAndSet比对完值后才设置新值的方式即为CAS:无锁、乐观锁、轻量级锁,synchroznied存在线程阻塞、上行文切换、操作系统调度比较费时;CAS一直循环比对执行,效率要高
(4)compareAndSetInt底层使用native修饰,底层是c++代码,实现了原子性问题,在汇编语言使用代码lock cmpxchqq保证了原子性,是缓存行锁
(5)ABA问题:线程一那到一个变量,线程二执行比较快,也拿到这个变量,把变量的值进行修改,再快速修改回原来的值,这样变量的值有过一次变化,线程一再去执行compareAndSet的时候,虽然值还是之前的没变,但是已经发生过变化了,出现ABA问题
(6)解决ABA问题就是给变量加版本,每次操作变量版本加1,JDK带版本的锁有AtomicStampedReference,这样就算变量被其它线程修改过再回复原值,版本号也是不一致的。
7.锁优化
(1)重量级锁会把等待的线程放到队列中,重量级锁锁定的是monitor,存在上下问切换的资源占用;轻量级锁若是线程太多,会存在自旋,耗费cpu
(2)jdk1.6之后,锁升级为无状态-》偏向锁(锁id指定)-》轻量级锁(自旋膨胀)-》重量级锁(队列存储)
(3)创建一个对象,此时对象为无状态,当启动了一个线程时,再创建一个对象时,启用偏向锁,偏向锁执行完之后不会释放锁;当再启用一个线程时,有两个线程来挣抢对象时,立马又偏向锁升级为轻量级锁;当再创建一个线程的来挣抢对象锁时,由轻量级锁升级为重量级锁
(4)分段CAS,底层有一个base记录变量值,当有多个线程类访问此变量是,base的值会分为多个cell,组成数组,每个cell对应一到多个线程的cas处理,避免了线程的自旋空转,这样还是轻量级锁,返回数据的时候,底层调用的是所有cell数组和base的加和
public class Num {
LongAdder longAdder = new LongAdder();
public void incrent() {
longAdder.increment();
}
public long getNum(){
return longAdder.longValue();
}
}
public long longValue() {
return sum();
}
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
来源:https://blog.csdn.net/ZHANGLIZENG/article/details/115710833


猜你喜欢
- 目录概述ClassPoolCtClassCtMthodCtFieldCtConstructorClassPathClassLoader示例创
- 网络中数据传输经常是xml或者json,现在做的一个项目之前调其他系统接口都是返回的xml格式,刚刚遇到一个返回json格式数据的接口,通过
- RecyclerView上拉加载,先看效果:网上有很多这类得框架,不过在自己的项目只用到上拉加载的功能,所以自己封装一个简单点的。主要依赖B
- 方式一public class Test{ public static void main(String[] args) throws Ex
- 题目给定count=0;让5个线程并发累加到1000;思路创建一个类MyRunnable,实现Runnable(继承Thread类也可)定义
- 一、什么是Spring Cloud?SpringCloud 对常见的分布式系统模式提供了简单易用的编程模型,帮助开发者构建弹性、可靠、协调的
- 前言参数绑定,简单来说就是客户端发送请求,而请求中包含一些数据,那么这些数据怎么到达 Controller ?这在实际项目开发中也是用到的最
- spring profile 多环境配置管理现象 如果在开发时进行一些数据库测试,希望链接到一个测试的数据库,以避免对开发数据
- 接口定义了一系列的行为规范,为类型定义一种Can-Do的功能。例如,实现IEnumerable接口定义了GetEnumerator方法,用于
- jsoup是一个非常好用的html解析工具。使用时需要下载相应的jar包。下面就是我使用jsoup解析html的表格的java源
- JNA(Java Native Access):建立在JNI之上的Java开源框架,SUN主导开发,用来调用C、C++代码,尤其是底层库文件
- 前言缓存主要是为了提高数据的读取速度。因为服务器和应用客户端之间存在着流量的瓶颈,所以读取大容量数据时,使用缓存来直接为客户端服务,可以减少
- 首先给大家声明一点:需要 jdk 7 , tomcat需要支持websocket的版本 1.InitServlet &n
- 本文实例讲述了Java+Ajax实现的用户名重复检验功能。分享给大家供大家参考,具体如下:今天,我来教大家怎么实现Java+Ajax实现用户
- 一、业务背景有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响。二、分析流程
- Spring Cloud Gateway去掉url前缀主要是增加一个 route,其他配置不变routes: - id: ser
- 1使用背景在实际项目中其中一部分逻辑可能会因为调用了外部服务或者等待锁等情况下出现不可预料的异常,在这个时候我们可能需要对调用这部分逻辑进行
- 图的实际应用在现实生活中,有许多应用场景会包含很多点以及点点之间的连接,而这些应用场景我们都可以用即将要学习的图这种数据结构去解决。地图:我
- 一、简介  Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它
- 本文实例为大家分享了Android仿Iphone屏幕底部弹出效果的具体代码,供大家参考,具体内容如下main.xml如下: <?xml