多线程与高并发

多线程与高并发

CAS

compare and swap

比较并替换
过程
原值   == 》 O
读取值  == 》 E = O
计算值  == 》 A = 计算E
比较值  E == O ? O = A : E = O重复过程

ABA问题

当线程A读取到值后,原值被其他线程修改,最终又回到原值,但已经不是之前的值,这时线程A进行比较原值,还会进行之前的操作,但操作的已经不是之前的原值

# 解决方法
使用版本号
每次进行操作,都对版本号进行修改
比较时,带上版本号一起比较

JDK中的类

AtomicStampedReference
/**
 * AtomicStampedReference类型中使用了版本戳,解决了ABA问题
 * 但是可能在比较之后写入的过程中会导致ABA问题
 * 追溯底层源码,compareAndSwap方法底层使用c++语言调用汇编语言方法
 * lock cmpxchg  执行方法时,锁定cpu不允许其他cpu操作				compareAndExchange
 */
String a = "aaa";
AtomicStampedReference i = new AtomicStampedReference(a, 1);
i.compareAndSet(i.getReference(), i.getReference() + "b", i.getStamp(), i.getStamp() + 1);
System.out.println(i.getReference());

对象在内存中的内存布局

JOL Java Object Layout

<dependencies>
    <dependency>
        <groupId>org.openjdk.jol</groupId>
        <artifactId>jol-core</artifactId>
        <version>0.9</version>
    </dependency>
</dependencies>
 public static void main(String[] args) {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
 }
64位系统默认都是8个字节
markword 8个字节
class pointer 默认是压缩指针  java开启时默认参数 UsePrecessedClassPointer 4个字节

markword + class pointer = 对象头

instence data 实例数据 无数据默认 0 
padding 补齐 字节为8的倍数效率最好,所以padding会使用空字节补齐

所以在默认压缩的情况下  8 + 4 + 4() = 16个字节
所以 Object o = new Object() 16个字节 如果加上引用对象 o 就是20个字节  引用对象指针默认也开启压缩指针
Class User{
	private int id;
	Private String name;
}

markword 8
class pointer 4
int 4
String 引用对象 4
8 + 4 + 4 + 4 = 20  + 4() = 24
给对象加锁时,锁的信息保存在markword中
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());

synchronized (o) {
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

锁升级过程

new - 偏向锁 - 轻量级锁(自旋锁(无锁)、自适应自旋) - 重量级锁

#锁升级过程
1.无锁态 :存储对象的hashcode
2.偏向锁 :当只有一个线程调用时,会将线程指针存在对象的markword中
3.轻量级锁(自旋锁): 当出现资源的竞争关系时,就会取消偏向锁,线程栈中生成Lock Record指针,抢占对象资源,将Lock Record存入markword中,一直自旋,直到获取到资源,占用cpu
4.重量级锁(互斥锁): 向操作系统申请锁资源,会将占用资源外的其他线程放入队列等待,不占用cpu资源

锁消除

Public void add(String s1, String s2){
    StringBuffer s = new StringBuffer();
    s.append(s1).append(s2);
}

StringBuffer在add方法内部,不会被线程共享,jvm会自动消除StringBuffer中的锁,提高cpu效率

锁粗化

StringBuffer s = new StringBuffer();
String str = "abc";
int i = 0;
while(i < 100){
    s.append(str);
    i++;
}

jvm检测到一连串的操作都对同一个对象加锁,就会将锁粗化到外部,while循环

Synchronized实现过程

1.java层面 :synchronized
2.编译字节码:monitorenter开启监视器 monitorexit退出监视器
3.锁自动升级
4.lock comxchg

AQS原理

AbstractQueuedSynchronizer类

抽象的同步队列  ==> 解决数据安全问题

核心思想

AQS的核心思想是如果当前线程请求的共享资源是可用的,则让线程操作当前共享资源,并将共享资源进行锁定
如果当前线程请求的共享资源是锁定状态,则将线程保存到阻塞队列
AQS使用了双向链表保存阻塞队列
state变量保存锁的状态,0为资源可用,1为资源锁定 >1为重入锁 
**重入锁**,当前获取锁的线程再次获取锁,state+1
state的改变在原子性的基础上进行改变

Threadlocal

使用完Threadlocal后需要remove,不然会造成内存泄漏
ThreadLocalMap的key为ThreadLocal对象 ThreadLocal采用弱引用,当ThreadLocal使用完制空时,value还存在,就会造成内存泄露
所以在使用完ThreadLocal之后要进行remove
ThreadLocal三个方法  set get remove
ThreadLocal是本地线程变量,在当前线程中是对其他线程隔离的,ThreadLocal可以为线程创建共享变量的副本,比如多个线程同时操作一个单例对象中的成员变量,可以使用ThreadLocal,就不会造成混乱
使用场景
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

volatail

volatail可以保证变量的可见性
JMM内存模型中,每个线程都会生成自己的内存空间,对共享变量都会复制一个副本进行操作,所以会导致数据不能即时更新
volatail保证数据的可见性,当共享数据被改变时,所有副本都能看到并作出改变,但volatail不能保证变量的原子性,因为数据操作指令可能是多条

Session会话管理。




## volatail

```bash
volatail可以保证变量的可见性
JMM内存模型中,每个线程都会生成自己的内存空间,对共享变量都会复制一个副本进行操作,所以会导致数据不能即时更新
volatail保证数据的可见性,当共享数据被改变时,所有副本都能看到并作出改变,但volatail不能保证变量的原子性,因为数据操作指令可能是多条
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值