字节跳动

目录

计算机网络

JUC

1.如何减少上下文切换

2.介绍volatile

3.java内存模型

5.CAS

6.AQS


计算机网络

HTTP

JUC

1.如何减少上下文切换

减少上下文切换的方法有无锁并发编程,CAS算法,使用最少线程和使用协程

无锁并发编程:多线程竞争锁时,会引起上下文切换,所以可以用一些方法来避免使用锁,比如将数据的id按照hash算法取模分段来实现,让不同的线程处理不同段的数据

CAS算法:java的Atomic包使用的是CAS算法来更新数据,而不需要加锁,也就不会有上下文切换了

使用最少线程:避免创建一些不必要的线程,比如说,很少的任务创建了大量的线程,导致大量线程处于等待状态

协程:在单线程中实现多任务的调度,并在单线程里维持多个任务间的切换

2.介绍volatile

volatile主要作用有两个,内存可见性和禁重排

内存可见性指的是,当一个线程修改共享变量时,另一个线程可以读到修改的值

它的底层实现原理是,volatile修饰的共享变量在进行写操作的时候,底层会多出一个lock前缀指令,这个lock前缀指令做了两件事情,一个是将当前处理器缓存行的数据写回到系统内存,还一个就是这个写回内存的操作会是其他CPU里缓存了该内存地址的数据无效

通常情况下,为了提高处理速度,处理器是不会和内存直接进行通信的,而是将系统内存的数据先读取到内部缓存,但是操作中就不知道这些数据什么时候写回到系统内存;如果对声明了volatile的变量进行修改时,jvm就会向处理器发送一条lock的前缀指令,就会使这个变量所在的缓存行中的数据写回到系统内存,当其他处理器发现自己缓存的数据行对应的内存地址被修改时,就会将当前处理器的缓存数据失效,当这个处理器在对这个数据进行修改操作时,就会重新把数据从系统内存中加载到缓存中

可以通过追加64位字节来提高性能,像LinkedTransferQueue,它在使用volatile变量的时候用一种追加字节的方式来优化队列的出队和入队的性能;缓存行是64字节宽,如果队列的头结点和尾节点都不足64字节的话,处理器会将他们读到同一个缓存行中,在多个处理器下,每个处理器都会缓存相同的头尾节点,当一个处理器试图修改头结点的时候,会将整个缓存行锁住,在缓存一致性的作用下,会导致其他处理器不能访问自己Cache中的尾节点,而队列需要频繁的修改队头和队尾节点,在多处理器的情况下会严重影响入队和出队的效率

有两种情况下不需要追加64字节,缓存行非64字节的处理器,共享变量不会被频繁的修改,因为使用追加字节的方式,需要处理器读取更多的字节到缓冲区,这本省就是一定的性能消耗,如果共享变量没有频繁的写操作的时候,锁的几率就比较小,没有必要通过追加字节的方式来避免互相锁定

3.java内存模型

java内存模型: 线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存存储了该线程共享变量在主内存中的副本.线程对变量的所有操作都在工作内存中进行,而不能直接读写主内存中的数据,也不能访问其他线程的工作内存;它的工作流程就是,当一个线程A把本地内存中更新过的共享变量刷新到主内存中,另一个线程B到主内存中读取线程A已经修改的值

java内存模型的主要目的是定义程序中各种变量的访问规则,java中线程之间的通信就是由Java内存模型来控制的,JMM决定了一个共享变量的写入在什么时候是对另一个是线程可见的

在Java中的共享变量,所有的实例域,静态域和数组元素都存储在堆内存中,堆内存是所有线程共享的一块内存区域;局部变量,方法定义参数和异常处理参数是线程私有,不会被共享

4.伪共享

当CPU访问某个共享变量时,首先看CPU Cache中是否有该变量,如果有,则直接从中获取,否则在主存中获取该变量,把该变量所在的内存区域的一个Cache行大小的内存复制到Cache中.由于存放到Cache行的是内存块而不是单个变量,所以可能会把多个变量存放到一个Cache中.当多个线程同时修改缓存行中的多个变量的时候,同时只能有一个线程能够操作缓存行,这就是伪共享

在CPU与主内存之间存在高速缓冲存储器(Cache),一般集成在CPU内部,Cache内部是以行为单位存储,每一行称为一个Cache行,Cache行是Cache与主内存进行交换数据的基本单位,Cache行的大小一般为2的幂次方,一般为64字节

伪共享的解决方法:可以通过字节填充的方式,也可以用@sun.misc.Contended注解来解决

字节填充,也就是在创建一个变量的时候,使用填充字段来填充该变量所在的缓存行,这样就避免了将多个变量存放在了同一个缓存行中了

使用@sun.misc.Contended注解,用来解决伪共享的问题,像Thread类中的变量threadLocalRandom,threadLocalRandomProbe,threadLocalRandomSecondarySeed中就使用了这个注解,但是默认情况下,@Contended注解只用于java核心类,如rt包下面的类,如果用户需要使用这个注解,添加JVM参数-XX:-RestrictContended,填充的宽度默认为128,自定义宽度可以设置-XX:ContendedPaddingWidth参数

5.CAS

CAS比较并交换,CAS算法包括三个参数,要更新的变量(由对象内存位置和对象中变量的偏移量控制),变量的预期值和新值,当且仅当要更新的变量值等于预期值的时候,才会将新值设置为要更新的值,如果不成功,则循环再次尝试修改值

CAS是一种乐观锁,在访问之前不会加排它锁,而是在变量提交更新的时候,才会正式的去检测数据是否发生冲突

CAS的底层原理是Unsafe类和自旋锁

在多线程同时操作一个变量的时候,只有一个线程会操作成功,其他的都会失败,失败的线程不会被挂起,而是被告知失败,并且允许再次尝试,这就是自旋;

Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的所有方法都是直接调用操作系统底层资源执行相应的任务

变量valueOffset表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存的偏移地址来获取数据的

变量value用的是volatile修饰,保证了多线程之间的内存可见性

CAS是一条CPU并发原语,在调用Unsafe类中的CAS方法的时候,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过他来实现原子操作

由于CAS是一条系统原语,原语属于操作系统范畴,是由若干条指令组成的,用于完成某一个特定功能的过程,原语的执行过程不允许被中断,也就是说,CAS是一条CPU原子指令,也就不会造成数据不一致的问题

CAS有三大问题,

循环时间开销大,消耗CPU资源

只能保证一个共享变量的原子操作,但是对于多个共享变量来说,循环CAS就无法保证原子性了,可以把多个共享变量合并为一个共享变量来操作,如i=1,j=a合并为ij=1a;jdk提供的AtomicReference类用来保证引用对象之间的原子性,其中就可以把多个变量放置到一个对象中来进行CAS操作

ABA问题:解决思路就是在变量前面添加版本号,jdk中的AtomicStampedReference类可以用来解决ABA问题.这个类的compareAndSet方法的作用是,首先检查当前引用是否为预期引用,并且检查当前标志是否为预期标志,如果全部相等,则以原子的方式更新值

6.AQS

AQS是一个抽象队列同步器,通过维护一个共享资源状态(volatile int state)和一个先进先出(fifo)的线程等待队列,来实现一个多线程访问共享资源的同步框架

AQS的原理是,为每个共享资源都设置一个共享资源锁,线程在访问共享资源时,首先会尝试获取共享资源锁,如果成功,则当前线程便可以使用共享资源,如果失败,则将当前线程放入线程等待队列,等待下一次的调度

AQS只是一个框架,只定义了一个接口,具体资源的获取,释放都是由具体的自定义同步器实现,不同的自定义同步器争用共享资源的方式也不同,自定义同步器只需要实现共享资源state的获取和释放即可,具体的线程等待队列的维护,获取资源失败入队,唤醒出队等,AQS在顶层已经实现好了,不需要具体的同步器再做处理,自定义同步器主要要实现这些方法

state是volatile类型,只能保证可见性,不能保证原子性,state的原子性是由getState(),setState(),compareAndSetState()这三个原子操作来保证的

AQS共享资源的方式有两种,独占式和共享式

独占式只有一个线程能够执行,如ReentrantLock,共享式多个线程同时执行,如Semaphore,CountDownLatch. 

ReentrantLock为独占式,state初始值0为无锁状态,在线程执行tryAcquire方法获取锁后,state状态+1,线程独占ReentrantLock锁,这时其他线程通过trAcquire方法获取该锁会失败,直到线程释放锁,state状态变为0,其他线程才有可能获取到锁.线程释放锁之前,可以重复获取这个锁,即ReentrantLock为可重入锁,每获取一次,state+1,每释放一次,state-1,获取多少次就要释放多少次,这样才能保证state最终为0;如果获取锁的次数大于释放锁的次数,则该线程一直持有这个锁,如果获取锁的次数少于释放锁的次数,程序会报锁异常

CountDownLatch将任务分为N个子线程去执行,将state初始化为N,N与线程个数一致,N个子线程并行执行,每个子线程执行完成后调用countDown方法一次,state执行CAS操作让state-1,在所有子线程都执行完成即state=0的时候会unpark主线程,然后主线程会从await返回,继续执行后续动作

一般来说自定义同步器要么采用独占方式,要么采用共享方式,实现tryAcquire,tryRelease或tryAcquireShared,tryReleaseShared其中一组即可.但也可以独占和共享方式同时存在,如ReentrantReadWriteLock

线程池

java线程池主要用于管理线程组及其运行状态,以便java虚拟机能够更好的利用CPU资源,线程池的主要作用是线程复用,线程资源管理,控制操作系统的最大并发数,以保证系统高效且安全的运行

java线程池的工作原理:JVM根据用户参数创建一定数量的可运行的线程任务,并将其放入队列中,在线程创建后启动这些任务,如果线程数超过了最大线程数,则超出数量的线程排列等候,在有任务执行完毕后,线程池调度器会发现可用的线程,进而再次从队列中取出任务并执行
线程池的创建

线程池7大核心参数,也即ThreadPoolExecutor构造器核心参数

四大拒绝策略

❑ AbortPolicy:直接抛出异常。

❑ CallerRunsPolicy:只用调用者所在线程来运行任务。

❑ DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

❑ DiscardPolicy:不处理,丢弃掉。

 

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页