java并发知识

进程:是开辟运行程序空间环境的,系统进行资源分配和调度的基本单位。一个应用在启动时会像电脑申请一块儿内存以便运行自己的程序,内存中有堆有栈有方法区,用来储存资源。

线程:是用来执行程序命令的,所有线程可共享所属进程资源,线程是依赖进程存在的,无法独立存在。一个进程下允许存在N条线程,线程的创建和销毁都是在程序中灵活控制的,为了程序运行快就需要多条线程去执行不同的命令。

并发:指多个线程同时在执行并且会发生阻塞情况。例子:有1座桥,平时过桥30秒,高峰期人挤人过桥60秒,因为桥的吞吐量小于人数,所以发生人挤人的情况最终影响大家过桥的速度。大家都在拥挤过桥互相干扰。

并行:指多个线程同事执行但不会阻塞。例子:有10座桥,平时过桥30秒,高峰期因为桥多不会拥挤所以也是30秒,因为桥的吞吐量始终和人数成正比。大家都在平行过桥互不干扰。

同步/原子性:当一个资源被多个线程争相写入修改时,需要有先后顺序的修改写入,这种有秩序的行为称为同步。

异步:给一条线程下发命令去运行某个方法,什么时候运行完不关心,返回值也不关心,只管让其去运行即可。一般用于日志记录,消息派发等等功能。

CPU:用来运算数据产生结果的。其中包含:

  寄存器(一级缓存):用来存储计算结果的。

  运算器:正真运算的,运算的结果会保存到寄存器中。

  控制器:当运算器运算完结果保存到寄存器中后负责将结果刷新到二级缓存(多cup共用的缓存,若只有一个cup就不会有二级缓存,只有主内存和寄存器),再刷新到主内存。

内存不可见:假如有2个CPU,分别是A,B。A更新值x; 会先去寄存器中找x,找不到再去二级缓存中找,仍然没有就直接去主内存中找,若没有则创建。找到后赋值为x= 1;然后依次刷新到主内存(寄存器 -> 二级缓存 -> 主内存)。

      此时B也更新X,最终在二级缓存中找到x = 1,然后更新为x = 2,之后由寄存在 -> 二级缓存 -> 主内存(刷新结果),此时结果是:A寄存器x = 1. B寄存器x = 2, 二级缓存x= 2, 主内存x= 2;.

      发现结果了吧:A和B的寄存器x数据不一致,这就是内存不可见。内存不可见就会导致数据错误。

内存可见性/关键字:volatile:底层数据一经修改所有获取此数据的人都会第一时间知道。为了解决内存不可见java提供了volatile关键字,使用此关键字描述变量,就会在变量发生变化的第一时间系统会通知所有内存此变量的最新值,以达到内存可见的目的。  

关键字:synchronized:作用是保证同步并且拥有可见性。底层原理是:当某个线程获取到它时:它的计数器+1,然后记录是哪条线程占用了它。并且它时可重入锁,就是在synchronized方法中还可以访问synchronized只不过计数器还会加1,当程序执行完毕后计数器清0,记录信息销毁,此时其他线程就可抢夺了。

CAS:CAS即Compare and Swap,其是JDK提供的非阻塞原子性操作,它通过硬件保证了比较—更新操作的原子性。JDK里面的Unsafe类提供了一系列的compareAndSwap*方法.。包括redis中也提供了一些ACS操作,就是给出一个期待值,再给出一个目标值。当期待值符合预期就会变为目标值并返回true,反之不变返回false.如:RedisAtomicLong的机制,自增1,原子性操作,更多例子参考:https://www.cnblogs.com/wuyx/p/10480349.html

    boolean compareAndSwapLong(Object obj, long valueOffset, longexpect, long update)方法:其中compareAndSwap的意思是比较并交换。

    CAS有四个操作数,分别为:对象内存位置、对象中的变量的偏移量、变量预期值和新的值。其操作含义是,如果对象obj中内存偏移量为valueOffset的变量值为expect,则使用新的值update替换旧的值expect。这是处理器提供的一个原子性指令。

    JDK的rt.jar包中的Unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法,它们使用JNI的方式访问本地C++ 实现库。   

package mr.li.test.util;

import java.lang.reflect.Field;

import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import org.springframework.stereotype.Component;

import sun.misc.Unsafe;

@SuppressWarnings("restriction")
@Component
public class TestUnsafe {

    static Unsafe unsafe;
    static long stateOffset;
    private int code2;
    private long code1;

    static {
        try {
            // 使用反射获取Unsafe的成员变量theUnsafe
            Field fieId = Unsafe.class.getDeclaredField("theUnsafe");
            // 设置为可存取
            fieId.setAccessible(true);
            // 获取该变量的值
            unsafe = (Unsafe) fieId.get(null);
            // 获取student的code在在TestUtil中的偏移量
            stateOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("code1"));
        } catch (Exception e) {
            System.out.println(e.getLocalizedMessage());
            throw new Error(e);
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, SecurityException {

        // 方式一获取Unsafe:采用反射获取,因为这个方法比较敏感所以jdk不会让我们轻易调用。java14以后这种反射调用私有方法的行为不可行了,被限制了。这里用的是java8
        TestUnsafe testUnsafe1 = new TestUnsafe();
        testUnsafe1.setCode1(222L);
        boolean is2 = unsafe.compareAndSwapLong(testUnsafe1, stateOffset, 222L, 55555L);
        System.out.println("方式1:Long类型ACS操作" + is2 + " -> " + testUnsafe1.getCode1());// 方式1:true -> 55555

        // ----------------------------------------------------------------------------------------------------

        // 方式二获得Unsafe:使用spring提供的工具包获取。
        Unsafe unsafe1 = UnsafeUtils.getUnsafe();
        TestUnsafe testUnsafe2 = new TestUnsafe();
        testUnsafe2.setCode2(1112);
        // 获取Student中的code在TestUtil中的偏移量
        long offset = unsafe1.objectFieldOffset(TestUnsafe.class.getDeclaredField("code2"));
        // 操作对象,偏移量, 原来的值, 需要变更新的值
        boolean is = unsafe1.compareAndSwapInt(testUnsafe2, offset, 1112, 222);
        System.out.println("方式2:int类型ACS操作:" + is + " -> " + testUnsafe2.getCode2());// 方式2:true -> 222
    }

    public int getCode2() {
        return code2;
    }

    public void setCode2(int code2) {
        this.code2 = code2;
    }

    public long getCode1() {
        return code1;
    }

    public void setCode1(long code1) {
        this.code1 = code1;
    }
}

伪共享:以拷贝主内存数据到各自缓存已到达各个线程数据一致的情况我们称之为伪共享。为了解决计算机系统中主内存与cpu之间运行速度差问题,会在cpu和主内存之间加入一些高速缓冲储存器(cache)。通俗将就是为了快不让cup做一些无用功,就会将计算结果储存在缓存器中,当下次有同样的计算需求时直接从缓存中拿结果而不用去计算。让好钢用在刀刃上。但是因为1级缓存(上面说的寄存器),二级缓存都属于拷贝的主内存结果,所以他们自己运算后才会把结果刷新到主内存,缓存并不是主内存所以看似所有线程数据一致,其实都是拷贝的主内存的结果。这种情况我们称之为“伪共享”。

锁的类型介绍,下面的介绍都是锁的特性,一种锁可能占好几个特性例如synchronized拥有:悲观,非公平,独占,可重入等特性

悲观锁:要访问数据就要排队。像:synchronized就是悲观锁。悲观锁起源于数据库。

乐观锁:先执行程序当最后提交时看看数据是不是预期值,若是:成功,不是:回滚,挂起或轮询。有点CAS的意思.乐观锁的主要思想是,并没有那么多的并发,运气不好我就回滚,但直接成功(没人和我争)的可能性很大。

公平锁:先来后到顺序抢占资源,ReentrantLock pairLock = new ReentrantLock(true)。

非公平锁:一旦锁资源释放各位各凭本事抢资源,谁抢到就谁的其他人继续等着去。ReentrantLock pairLock = new ReentrantLock(false)一般默认是非公平锁,因为公平锁还需要线程额外维护,若非必要就是浪费性能。

独占锁:见名知意,自己将锁占住只要释放了其他人才能拥有。ReentrantLock, synchronized

共享锁:大家都可以拥有锁资源,例如:某个文件的读就是共享锁:大家在不改变资源的前提下都可以读,但不可以写。ReadWriteLock,共享锁也是一种乐观锁。

可重入锁:允许在占有锁资源的前提下又去抢占锁资源,synchronized就是可重入锁。

自旋锁:当未抢到资源是就会轮询不停抢,知道一定次数后还没抢到则会挂起。

 

AtomicLong,AtomicInt,AtomicBoolean都是jdk提提供的原子性操作类,底层使用的是Unsafe来实现,这种CAS方式就是大家一起来自增,只有一个自增成功其他的都自旋,知道自己抢到为止。这种特点是性能要比锁好很多,缺点就是性能开销大。所以java又提供了一种用于高并发下的原子操作类,jdk8提供:LongAdder.

AtomicLong示意图:

 

使用AtomicLong时,是多个线程同时竞争同一个原子变量。

 

LongAdder示意图:

 

 使用LongAdder时,则是在内部维护多个Cell变量,每个Cell里面有一个初始值为0的long型变量,这样,在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。

 

posted @ 2021-12-10 16:28  ~~mr.li~~  阅读(58)  评论(0)    收藏  举报