java并发编程知识点汇总

文章目录

  • 1. Java8新特性
    • 1.1 Lambda表达式
    • 1.2 函数式接口
    • 1.3 Stream流式计算,应用了上述函数式接口能力
    • 1.4 接口增强
  • 2. 常用原子类
  • 3. 多线程与高并发-juc
    • 3.1 谈一谈对volatile的理解
    • 3.2 谈一谈对JMM的理解
    • 3.3 谈一谈对CAS及底层原理的理解
    • 3.4 谈一谈对ABA问题及原子更新引用的理解
    • 3.5 谈一谈集合不安全的情况
    • 3.6 谈一谈Java常用锁
    • 3.7 谈一谈常见的线程计数器
    • 3.8 谈一谈阻塞队列
    • 3.9 谈一谈线程池以及ThreadPoolExecutor
    • 3.10 谈一谈死锁以及定位分析
    • 3.11 谈一谈强引用、软引用、弱引用和虚引用
    • 3.12 谈一谈六种常见的OOM
  • 4. JVM核心知识点
    • 4.1 JVM垃圾回收如何确定垃圾及GC Roots
    • 4.2 常用的JVM基本配置参数
    • 4.3 垃圾收集算法与垃圾回收器
    • 4.4 Linux系统变慢如何定位
    • 4.5 Github骚操作

1. Java8新特性

在这里插入图片描述

1.1 Lambda表达式

	java8引入了一个新的操作符"->",该操作符将表达式拆分为两部分:左侧:Lambada表示表达式的参数列表右侧:Lambda表示所需要执行的功能规则:(左侧)->(右侧)new Thread( ()-> System.out.println("abc")).start();

1.2 函数式接口

package com.gwolf;
@FunctionalInterface
public interface MyPredicate<T> {public boolean test(T t);
}

1.3 Stream流式计算,应用了上述函数式接口能力

StreamAPI是java8中处理集合的关键抽象概念,它可以指定您希望对集合进行的操作,
可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。

1.4 接口增强

Java8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:

在这里插入图片描述

2. 常用原子类

引入原子操作类的原因?

  1. 在多线程环境下,对于Java中的运算操作(例如自增或自减),例如num++解析为num=num+1,显然这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题,线程不安全。
  2. 因此需要引入各种原子操作类。AtomicInteger提供线程安全的原子操作方式来进行相关运算(包括加减),因此原子操作类十分适合多线程、高并发情况下的使用。

在这里插入图片描述

1AtomicBoolean: 	原子更新布尔类型
(2AtomicInteger: 	原子更新整型
(3AtomicLong: 			原子更新长整型以上3个类提供的方法几乎一模一样,以AtomicInteger为例进行详解,AtomicIngeter的常用方法如下:
AtomicInteger构造方法
1public AtomicInteger(int initialValue)  创建具有给定初始值的新 AtomicInteger
2public AtomicInteger()             			创建具有初始值0的新 AtomicInteger AtomicInteger常用API1public final int get()              		获取当前值
2public final void set(int newValue)    设置为给定值
3public final boolean compareAndSet(int expect, int update)CAS)参数:expect - 预期值  update - 新值 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值,返回 true如果当前值 != 预期值,返回 false,说明实际值与预期值不相等
4public final int getAndIncrement()       以原子方式将当前值加 15public final int getAndDecrement()       以原子方式将当前值减 16public final int incrementAndGet()       以原子方式将当前值加 17public final int decrementAndGet()       以原子方式将当前值减 18public final int getAndAdd(int delta)     以原子方式将给定值与当前值相加。
9public final int addAndGet(int delta)       以原子方式将给定值与当前值相加。

3. 多线程与高并发-juc

主要包括volatile、jmm 、可见性、原子性、有序性 、AtomicInteger、 CAS 、ABA问题、Unsafe类、自旋锁 、 ABA问题、原子引用更新、如何规避ABA问题

3.1 谈一谈对volatile的理解

A、volatile是Java虚拟机提供的轻量级的线程同步机制(轻量级的synchronized)

特性:
1、保证可见性 testvolatile01.java
2、保证有序性(禁止指令重排列) testvolatile03.java
3、不保证原子性 testvolatile02.javas

可见性:某一线程中的变量发生变化,其他线程可立刻得到通知,拿到该变量的最新值

原子性:也叫做完整性,不可分割,即某个线程在做某个具体操作时,要么不执行,要么彻底执行,中间不可以加塞或者被分割。

有序性(禁止指令重排列):有序性只针对多线程,计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排列,一般分为三种:

在这里插入图片描述

​ 单线程环境可以确保程序最终执行结果和代码顺序执行的结果一致,但在多线程环境中,多个线程交替执行,由于编译器优化重排列的结果,两个线程使用的变量能否保证一致性是无法确定的。(类似于做题,先做简单题,后做难题。多线程环境下程序执行也不是按顺序执行的,但满足happens before原则(指令之间的数据依赖性))

​ 在多线程环境下,指令重排会导致变量值不一致,必须禁止指令重排列,volatile修饰的变量就可以禁止指令重排。

B、volatile为什么不能保证原子性?

一个被volatile修饰的变量,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行n++这样的非原子操作,如果多个线程操作n++,就会出现写值丢失的情况,线程不安全,也就不会保证这个变量的原子性了。

在这里插入图片描述

C、既然volatile不保证原子性,如何解决?

1、synchronized方法进行加锁,效率低;

2、使用原子包装类Atomic,保证线程安全。

例如:AtomicInteger n=new AtomicInteger(),默认为0,

通过其方法atomic_i.getAndIncrement(); // 原子操作+1 实现原子操作

D、你在哪些地方使用过volatile?

1、单例模式:并发多线程环境下,懒汉单例模式,按道理应该实例化一次,实际却实例化很多次(而且不确定次数)【8种单例模式】

如何解决:

​ a、方法1 synchronized: public synchronized static testSingletonDemo getInstance()

​ b、方法2:DCL(Double Check Lock 双端检锁机制)+ volatile关键字

2、CAS

示例:

  1. 饿汉单例模式: 类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快(线程安全)

在这里插入图片描述

  1. 懒汉单例模式(线程不安全)

在这里插入图片描述

  1. 懒汉单例模式优化-001(线程安全:加锁synchronized)

在这里插入图片描述

  1. 懒汉单例模式优化-002(线程安全:volatile+双端检锁机制)

在这里插入图片描述

3.2 谈一谈对JMM的理解

JMM(Java内存模型)本身是一种抽象的概念,并不真实存在,它描述的是一组规范或规则,通过这组规范定义了程序中各个遍历的访问方式。

JMM特性:可见性、原子性、禁止指令重排列(保证有序性)

JMM关于线程同步的规定:

​ 1、线程加锁前,必须读取主内存中共享变量的最新值读到自己的工作内存

​ 2、线程解锁前,必须把共享变量的值刷新到主内存。

​ 3、加锁、解锁是同一把锁。

​ 由于JVM运行程序的实体是线程,而每个线程创建时JVM会为其分配一个工作内存(也叫做栈空间),工作内存是每一个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享区域,所有线程都可以访问。但线程对变量的读写操作必须在自己的工作内存中进行,因此,

​ 1、首先线程要将变量从主内存拷贝到自己的工作内存

​ 2、然后线程在自己的工作内存对变量进行操作

​ 3、操作完成后再将变量的值写回主内存

​ 各个线程的工作内存存储的是主内存的变量副本拷贝,不能直接操作主内存的变量。因此不同线程无法访问对方的的工作内存,线程间的通信必须通过主内存来完成,过程如下:

在这里插入图片描述

JMM线程安全性如何得到保证?

1、工作内存与主内存同步延迟导致的可见性问题,可以通过synchronized或volatile关键字解决,可以使一个线程修改后的变量立即对其他线程可见。

2、对于指令重排导致的有序性问题,可以通过volatile关键字解决,它可以禁止指令重排优化。

3.3 谈一谈对CAS及底层原理的理解

CAS是Compare-And-Swap,它是一条CPU并发原语,它的功能是判断内存某个变量的值是否为期望值,如果是则返回true,并更改为新值,如果不是则返回false,这个过程是原子操作过程。

典型应用是原子操作类AtomicInteger的compareAndSet方法:
compareAndSet(int expect, int update)
如果主内存真实值 == 线程期望值,则以原子方式将该值设置为给定的更新值,返回 true
如果主内存真实值 != 线程期望值,返回 false,说明实际值与预期值不相等

再次强调,CAS是一种系统原语,由若干条指令组成,原语的执行必须是连续的,在执行过程中不可以中断,也就是说CAS是一条CPU的原子指令,不存在数据不一致的情况,也就是线程安全。

CAS底层原理:Unsafe类与自旋锁

  1. Unsafe是CAS核心类,由于Java无法直接访问底层操作系统,需要调用native本地方法来访问,Unsafe相当于一个后门,基于Unsafe类可以直接像C语言指针一样操作特定内存中的数据,并直接调用底层资源执行相应任务。
  2. Unsafe类中的所有方法都是native修饰,Java中CAS操作的执行都依赖于Unsafe中的方法。

// 举例:getAndIncrement底层实现

getAndIncrement底层实现:底层getAndAddInt()采用CAS思想
this:          new* 出来的对象
VALUE:   			内存地址(内存偏移量)
1:		       	 变动的数量

在这里插入图片描述

执行过程:假设线程A和线程B并发执行getAndAddInt操作:

1、AtomicInteger里面的value值原始值为3,根据JMM,线程A、B各自持有一份值为3的变量副本在其工作内存。
2、线程A通过getIntVolatile得到value的值为3,此时线程A被挂起。
3、线程B也通过getIntVolatile得到value的值为3,并立即执行compareAndSetInt比较内存的值也为3,成功修改value=4,结束收工。
4、此时线程A恢复,执行compareAndSetInt,发现自己工作内存的value值为3,与主内存的4不一致,修改失败,只能重新读取主内存的value值。
5、线程A重新获取value值,因为value被volatile修饰,根据可见性原理它可以立即知道vlaue最新值,然后执行compareAndSetInt进行比较替换,知道成功。

总结:因为每个线程执行过程中多了一步将本线程的变量值与主内存变量值比较的过程,所以保证了线程安全。

CAS缺点1、由于通过循环do..while来实现,没有加锁,如果CAS长时间不成功,会给CPU带来很大的开销2、只能保证一个共享变量的原子操作3、引发ABA问题AtomicInteger为什么使用CAS不使用synchronized1synchronized是对方法加锁,同一时间段只能有一个线程访问,数据一致性得到保证,但是并发性下降;
2CAS使用do while语句,没有加锁,反复将期望值与真实值对比,即保证了数据一致性,也提高了并发性

3.4 谈一谈对ABA问题及原子更新引用的理解

ABA问题:俗称狸猫换太子,也即是说,CAS算法实现的一个重要前提是需要取出主内存某时刻的数据并在当下时刻比较并替换,那么在这个时间差内出现数据的变化,比如下面这种情况,尽管线程Thread01操作成功,但是并不代表这个过程没有问题。

在这里插入图片描述

那如何解决ABA问题?

​ 带时间戳的原子引用AtomicStampedReference

// todo 原子引用

3.5 谈一谈集合不安全的情况

  • HashMap
put(K, V)	 	 // 将键/值映射存放到Map集合中,key不允许重复,否则会覆盖已有的key对应的值,key、value允许为null
remove(key) 	//删除Map集合中键为key的数据并返回其所对应value值。
get(key) 			//返回指定键所映射的值,没有该key对应的值则返回null
isEmpty () 		//判断Map集合中是否有数据,如果没有则返回true,否则返回false
containsKey(Object key)  			//判断集合中是否包含指定键
containsValue(Object value)  	//判断集合中是否包含指定值
size()  											//返回Map集合中数据数量

1.底层数据结构:

​ jdk7:数组+链表,数组初始长度16,扩容数组大小增加为原来的2倍

​ jdk8:数组+链表+红黑树,数组初始长度16,当链表长度大于8,会将链表转为红黑树

2.实现原理

JAVA7:数组+链表

在这里插入图片描述

大方向上,HashMap 底层是一个数组,然后数组中每个元素是一个单向链表

put(key, value)执行过程:
1、根据输入的key,计算key的哈希值Hash(key),并得到对应数组下标:Hash(key)%N。
2、当计算得到的下标出现一致时,即哈希冲突,此时需要存储到上一个entry链表的下一个节点,HashMap通过链址法解决哈希冲突。

get(key) 执行过程:
1、首先根据key得到一个数组下标Hash(key)%N
2、然后比较该下标hash值与hash(key)值是否一致,若key值也一致,则返回对应的value(key、hash(key)都一致)

JAVA8:数组+链表+红黑树

根据 Java7 的介绍,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。

为了解决链表过长查询效率过低的问题,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,降低时间复杂度为 O(logN)。

在这里插入图片描述

put(key, value)执行过程:
1、根据输入的key,计算key的哈希值Hash(key),并得到对应数组下标:Hash(key)%N。
2、当计算得到的下标出现一致时,即哈希冲突,此时需要存储到上一个entry链表的下一个节点,HashMap通过链址法解决哈希冲突。

​ 3、当链表长度大于8时,将链表改为红黑树!!

  // java8默认类成员变量值static final int DEFAULT_INITIAL_CAPACITY =  1 << 4;	  // 默认初始容量16(必须是2的倍数),也就是数组初始化容量为16static final int MAXIMUM_CAPACITY = 1 << 30;						// 最大容量 1073741824 2^30static final float DEFAULT_LOAD_FACTOR = 0.75F;				// 默认扩充因子static final int TREEIFY_THRESHOLD = 8;								// 当链表长度大于此值时,将链表转成红黑树static final int UNTREEIFY_THRESHOLD = 6;							// 当红黑树元素小于此值时,将红黑树转换成链表static final int MIN_TREEIFY_CAPACITY = 64;						// 当链表长度大于此值且容量大于64时,才允许树形化链表(即将链表转换成红黑树)int threshold;	// threshold=capacity*loadFactor扩容阈值 表示当HashMap的size大于threshold时会执行resize(扩容)操作 final float loadFactor;// 装载因子=size/capacity,是用来衡量 HashMap 满的程度,而不是占用桶的数量去除以capacity

PS: 二叉搜索树-平衡二叉树-红黑树

在这里插入图片描述
在这里插入图片描述

口诀:黑根黑叶红不邻【2-3-4】,同祖等高只数黑【5】

在这里插入图片描述
在这里插入图片描述

  • 为什么ArrayList是线程不安全的?
ArrayList的底层实现:
1、用了一个Object[]的数组,用来保存所有的元素;
2、用了一个size变量,用来保存当前数组中已经添加了多少元素。

在这里插入图片描述

add方法线程不安全:
add方法添加一个元素的时候,它可能会有两步来完成:
1. 在elementData[size]的位置存放此元素;
2. 集合个数加1:size+1在单线程运行的情况下,如果size=0,添加一个元素后,此元素在位置elementData[0],而且size=1,没有问题
但在多线程情况下,比如有两个线程,1. 线程A先将元素存放在位置0,但还没有执行size+1操作,随后线程A暂停,线程B得到运行机会。【elementData[0]=A,size=02. 线程B也添加元素,因为此时size=0,所以线程B也将元素存放在位置0,并执行size+1操作。【elementData[0]=B,size=13. 线程A继续运行,执行size+1操作【此时elementData[0]=B,size=2】此时集合元素实际上只有一个,存放在位置0,而 size=2。这就是“线程不安全”了。
  • 为什么HashMap是线程不安全的?
Java7 HashMap线程不安全体现在:由于多线程对HashMap进行扩容,调用了HashMap#transfer(),导致线程死循环、数据丢失具体原因:某个线程执行过程中被挂起,其他线程已经完成数据迁移,等CPU资源释放后被挂起的线程重新执行之前的逻辑,数据已经被改变,造成死循环、数据丢失。

在这里插入图片描述

Java8 HashMap线程不安全体现在:由于多线程对HashMap进行put操作,调用了HashMap#putVal(),导致数据覆盖1. 假设线程AB都在进行put操作,并且hash函数计算出的插入下标是相同的2. 当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,3. 然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

在这里插入图片描述

1、故障现象java.util.ConcurrentModificationException2、导致原因并发线程争抢资源,出现数据不一致,导致写值丢失的现象。类似于花名册签字案例3、解决方案解决方案一: 使用各自线程安全的集合List<Object> al=new Vector<>();           				// ArrayList -> Vector(synchronized)Map<String,String>map=new ConcurrentHashMap<>();  // HashMap -> ConcurrentHashMap、HashTablePS:一般不采用HashTable,其效率低下,需要线程安全就使用concurrentHashMap,不需要线程安全就使用HashMap解决方案二:Collections的静态方法,同步线程Collections.synchronizedList(al);Collections.synchronizedMap(mp);Collections.synchronizedSet(st);解决方案三: CopyOnWrite容器,没有CopyOnWriteMapList<Object> al=new CopyOnWriteArrayList<>()Set<Object> set=new CopyOnWriteArraySet<>();
CopyOnWriteArrayList原理:写时复制、读写分离、lock
CopyOnWrite容器是一个写时复制的容器,往容器内添加元素的时候,不直接在容器内添加,而是1、先将当前容器Object[]进行复制copy,复制得到一个新的容器Object[] newElements2、然后向新容器添加元素,并将原容器的引用指向当前容器。(好处:可以对CopyOnWrite并发的读,而不需要加锁。因为当前容器不会添加任何元素)3、其他线程使用新的容器newElements。所有CopyOnWrite是一种读写分离的思想,读和写不同的容器。

在这里插入图片描述

3.6 谈一谈Java常用锁

主要包括:公平锁/非公平锁/可重入锁(递归锁)/自旋锁/独占锁(写锁)/共享锁(读锁)/读写锁

1. 公平锁:		在多线程环境下,按照线程申请锁的顺序来获取锁,性能不高,类似排队打饭,先来后到
2. 非公平锁: 指线程获取锁的顺序不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获得锁。线程执行效率提高,吞吐量大,但是在高并发的环境下,有可能会造成优先级反转或饥饿现象。
3. 可重入锁:	又称为递归锁,指同一线程外层函数获得锁之后,内层递归函数仍然能获取锁的代码,即同一线程在外层方法获取锁后,进入内层方法自动获取锁,可以防止死锁
4. 自旋锁:		 指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁,这样可以减少线程上下文切换的消耗,缺点是循环会消耗CPU资源。典型Unsafe.getAndAddInt() 
5. 读写锁:读时共享、写时独占可重入读写锁ReentrantReadWriteLock,基本上可以取代synchronizedprivate ReentrantReadWriteLock rwlock=new ReentrantReadWriteLock();rwlock.writeLock().lock();  //加写锁rwlock.readLock().lock();   //加读锁锁升级:1. 锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。2. 随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级为重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

3.7 谈一谈常见的线程计数器

a、CountDownLatch:让一些线程阻塞直到另一些线程完成一系列操作才被唤醒,初始值为target,计数器=0再执行(做减法):(以前我们都是第七个线程睡眠一段时间确保前六个线程执行完)CountDownLatch countDownLatch = new CountDownLatch(6);        countDownLatch.countDown();//计数器减1countDownLatch.await();//调用线程会被阻塞,当计数器为0时,才会被唤醒b、CyclicBarrier:字面意思是可循环使用的屏障,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门。初始值==0,计数器==target再执行(做加法)CyclicBarrier c=new CyclicBarrier(7,()->{System.out.println("最后执行操作,召唤神龙");});          c.await();//先来的线程,被阻塞c、Semaphore信号量机制:多个线程争抢多个共享资源。主要用于两个目的,一是用于多个共享资源的互斥使用,另一个是用于控制并发线程数目(争车位)Semaphore s=new Semaphore(3);//共享资源是3个停车位s.acquire();//占到车位s.release();//释放资源

3.8 谈一谈阻塞队列

阻塞队列:首先是一个队列,当阻塞队列为空时,从队列中获取元素的操作会被阻塞;当阻塞队列为满时,往队列里添加元素的操作将会被阻塞。

阻塞队列的优点:我们不需要决定什么时候去阻塞线程,什么时候去唤醒线程(不需要考虑wait、notify),因为这一切阻塞队列BlockingQueue一手包办了。在并发concurrent包发布之前,在多线程环境下,程序员需要控制这些线程同步的细节,并且兼顾系统的效率和安全性。

如何高效管理阻塞队列:ArrayBlockingQueue:  		由数组结构组成的有界阻塞队列LinkedBlockingQueue:  	由链表结构组成的有界阻塞队列(但大小默认为Integre.MAX_VALUE)也可以认为是无界阻塞队列(谨慎使用)SynchronousQueue:       不存储元素的阻塞队列,也即单个元素的队列(有且仅有1个元素)PriorityBlockingQueue:  支持优先级排序的阻塞队列DelayQueue:             使用优先级队列实现的延迟无界阻塞队列LinkedTransferQueue:    由链表结构组成的无界阻塞队列LinkedBlockingDeque:    由链表结构组成的无向阻塞队列

核心方法:

方法类型抛出异常特殊值阻塞超时
增加add(e)offer(e)put(e)offer(e,time,unit)
删除remove()poll()take()poll(time ,unit)
查询element()peek()NONO
抛出异常:当阻塞队列满时,再添加元素add(e)会抛出异常:lleagalStateException:Queue Full当阻塞队列空时,在删除元素remove()remove(e) 会抛出异常:NoSuchElementException特殊值:插入方法offer(),  成功为true,失败为false删除方法poll(),  成功为队内元素,失败为null阻塞:当阻塞队列满时,生产者再继续往队列添加元素put(e),队列会一直阻塞直到成功或者响应中断推出。当阻塞队列空时,消费者线程试图从队列take元素,队列会一直阻塞消费者进程直到队列可用。超时:建议使用阻塞队列用在哪里?1、生产者消费者模式(传统版、阻塞队列版)待定!!!2、线程池3、消息中间件   

3.9 谈一谈线程池以及ThreadPoolExecutor

线程池就是容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象、销毁线程的操作,避免系统资源的浪费。

线程池的优势?线程复用:         通过线程复用降低线程的创建和销毁造成的消耗提升响应速度:      当任务到达时,任务可以不需要等到线程创建就可以执行。提高线程的可管理性: 控制线程的并发数量,统一分配、管理线程常用的线程池
1Executors.newCachedThreadPool(int)       执行短期异步的小程序SynchronousQueue
2Executors.newFixedThreadPool(int)        执行长期的任务  LinkedBlockingQueue
3Executors.newSingleThreadExecutor(int)   任务按序执行    LinkedBlockingQueue
4Executors.newScheduledThreadPool(int)    延迟重复执行线程synchronized保证三大性:原子性,有序性,可见性
volatile保证有序性,可见性,不能保证原子性
  • 常用线程池底层实现

在这里插入图片描述

  • 自定义线程池ThreadPoolExecutor

在这里插入图片描述

1:核心线程数(corePoolSize):		线程池中常驻核心线程数
2:最大线程数(maximumPoolSize):	线程池能够容纳同时执行的最大线程数
3:keepAliveTime: 							非核心线程最大存活时间,若当前线程池数量超过核心线程数corePoolSize,当空闲时间达到keepAliveTime,多余非核心线程会被销毁直到只剩下corePoolSize个线程。
4:unit: 											keepAliveTime的单位
5:任务队列(workQueue):   			 被提交但尚未执行的任务,存放在阻塞队列中
6:线程工厂(threadFactory): 			用于创建线程,一般默认即可。
7:拒绝策略(handler):					 表示当等待队列满了并且工作线程大于等于线程池的最大线程数常见拒绝策略:AbortPolicy(默认):		 直接抛出异常RejectedExecutionException,阻止系统正常运行CallerRunsPolicy: 	  调用者优先策略,该策略不会抛弃任务,也不会抛出异常,而是将某些任务回退DiscardOldestPolicy:	抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务DiscardPolicy:				直接丢弃任务,不做任何处理也不抛出异常。如果允许任务丢失,可以使用
  • 底层原理

在这里插入图片描述

1、在创建了线程池之后,等待提交过来的任务请求
2、当调用execute()方法后添加一个请求任务2.1 如果正在运行的线程数量小于corePoolSize,则立即执行该任务2.2 如果正在运行的线程数量大于等于corePoolSize,则将该任务放进队列 workQueue2.3 如果任务队列满了,并且正在运行的线程数量小于maximumPoolSize,那么线程池创建非核心线程执行任务2.4 如果任务队列满了,并且正在运行的线程数量大于等于maximumPoolSize,那么线程池启动拒绝策略执行。3、当一个线程完成任务时,它会从队列取下一个任务执行
4、当一个线程无事可做超过一定的时间keepAliveTime,线程池会判断4.1 如果运行的线程数大于corePoolSize,那么会停掉该非核心线程4.2 最后线程池的线程数目收缩到corePoolSize的大小

3.10 谈一谈死锁以及定位分析

死锁:指的是两个或两个以上进程在执行过程中,因争夺资源而造成的互相等待的现象,若无外力干涉,那他们将无法继续推进下去,如果系统资源充足,进程的资源请求都能得到满足,死锁出现的可能性较低,否则会因争夺有限的资源而陷入死锁。(系统资源不足、进程推进顺序不合适、资源分配不当)

在这里插入图片描述

故障分析:

1、打开IDEA控制台
2、定位Java线程:jps -l

在这里插入图片描述

3、查看异常进程号:jstack 531

在这里插入图片描述

常用命令行:
1、jps:        查看本机后台java进程信息。 jps -l
2、jinfo:      查看正在运行Java程序相关信息jinfo -flag  配置项 进程号jinfo -flags 进程号
3、jstack:      打印线程的栈信息,制作线程dump文件(发现死锁)
4、jmap:        打印内存映射,制作堆dump文件   
5、jstat:       性能监控工具   
6、jhat:        内存分析工具   
7、jconsole:    简易的可视化控制台   
8、jvisualvm:   功能强大的控制台 
问题一:synchronizedvolatile区别1synchronized可以修饰方法,代码块,而volatile只能用于修饰变量。2、sychronized可以保证线程的原子性、可见性、有序性,而volatile能保证变量在多个线程之间的可见性、有序性,但不能保证原子性。3、多线程访问sychronized会出现阻塞,而访问volatile不会发生阻塞。4volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,但是sychronized关键字在执行效率上逐渐得到提升,在开发中使用synchronized关键字的比率还是比较大。问题二:synchronized和lock区别1synchronizedJava中的关键字,是内置的语言实现,而Lock是一个接口;2synchronized在发生异常时,会自动释放线程占有的锁,因此不会发生死锁;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此需要在finally块中释放锁;3、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。4Lock可以提高多个线程进行读操作的效率(读写锁)。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

3.11 谈一谈强引用、软引用、弱引用和虚引用

在这里插入图片描述

强引用:默认支持模式,当内存不足时,JVM会进行垃圾回收,对于强引用的对象,就算是出现了OOM,也不会对该对象进行回收。只要有一个强引用指向一个对象,就说明对象还活着,垃圾回收器不会回收这类对象。因此强引用是造成Java内存泄露的主要原因之一【JVM永不回收】

软引用:是一种相对强引用弱化了一些的引用,需要用java.lang.SoftReference类来实现,当系统内存充足时,该对象不会被回收,当系统内存不足时,该对象会被垃圾收集器回收。软引用通常应用在对内存敏感的程序中,比如高速缓存。【JVM内存够用的时候就保留,不够用就回收】

弱引用:弱引用需要用java.lang.weakReference类来实现,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会立即回收该对象占有的内存【JVM立即回收】

虚引用:虚引用需要java.lang.ref.PhantomReference类来实现,就是形同虚设,虚引用不会决定对象的生命周期**,在任何时候都会被垃圾回收器回收**,并且不能单独使用,必须要和引用队列ReferenceQueue联合使用。换句话说,虚引用的唯一作用,就是在这个对象被垃圾收集器回收的时候收到一个系统通知或者后续添加进一步的处理。

软引用应用场景:

假如有一个应用需要读取大量的本地图片:如果每次读取图片都从硬盘读取则会严重影响性能,如果一次性全部加载到内存中又可能造成内存溢出。此时使用软引用可以解决这个问题。设计思路:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

3.12 谈一谈六种常见的OOM

java.lang.StackOverFlowError                     	栈溢出:方法递归
java.lang.OutOfMemoryError:Java heap space        堆溢出:new大对象
java.lang.OutOfMemoryError:Metaspace             	元空间异常, 存储类信息、常量池、静态类
java.lang.OutOfMemoryError:Direct buffer memory   直接内存溢出(本地内存溢出),主要由NIO引起,ByteBuffer.allocate(capacity);       		// 分配JVM堆内存,属于GC管辖范围,由于需要拷贝速度较慢ByteBuffer.allocateDirect(capacity);   	// 分配本地内存,不属于GC管辖范围,若本地内存用光,那程序崩溃,抛出该异常java.lang.OutofMemoryError:GC overhead limit exceeded 	GC回收时间过长,超过98%时间进行垃圾回收GC,并且回收不到2%的堆内存
java.lang.OutOfMemoryError:unable to create native thread   无法创建新的native线程,准确来讲,该异常与对应的平台有关
导致原因:1、你的应用创建的太多的线程,超过系统承载极限2、你的服务器并不允许创建那么多的线程,如Linux默认单个进程只可以创建1024个线程
解决方法:1、降低线程的数量2、扩大Linux默认线程设置的限制

4. JVM核心知识点

4.1 JVM垃圾回收如何确定垃圾及GC Roots

垃圾:内存中已经不再被使用的对象被成为垃圾

  • 如何判断一个对象是否可以被回收?
    **引用计数法:**简单来说,给对象中添加一个引用计数器,每当有一个地方引用它,计数器加一,每当有一个引用失效,计数器减一,任何时刻对象的计数器的值为0,那么这个对象就是可回收对象。一般该方法已被舍弃,主要原因在于很难解决对象之间互相循环引用的问题。
    根搜索路径(枚举根节点做可达性分析):为了解决引用计数法的循环引用问题,Java使用通过GC Roots做可达性分析的根搜索路径方法。通过一系列名为“GC roots”的对象作为起点,从GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明该对象可回收。

在这里插入图片描述

GC Roots的对象:1、虚拟机栈中引用的对象            局部变量表2、方法区中的类静态属性引用的对象   static3、方法区中常量引用的对象          final4、本地方法栈native引用的对象      native

4.2 常用的JVM基本配置参数

JVM的参数类型1、标配参数:-version    -help    -showversion2X参数(了解):  -Xint:解释执行   -Xcomp:第一次使用就编译成本地代码   -Xmixed:混合模式3XX参数:重点!!!Boolean类型:-XX: + 属性值、-XX: - 属性值(+:开启、-:关闭)例:-XX:+PrintGCDetails                打印GC日志-XX:+UseSerialGC-XX:-UseG1GC     打开/关闭某垃圾收集器java -XX:+PrintFlagsInitial           查看JVM所有初始化参数java -XX:+PrintFlagsFinal            查看JVM所有修改、更新后的参数java -XX:+PrintCommandLineFlags -version       查看内存空间初始值KV设值类型:-XX:key=value常见参数:
-XX:NewSize=10m                  新生代
-XX:OldSize=60m                  老年代
-XX:PermSize=50m                 永久代
-XX:MaxPermSize=50m              永久代最大内存
-XX:MetaspaceSize=128            元空间, 理论上取决于物理内存,实际只有20MB-XX:MaxTenuringThreshold=15:     控制对象在经过多少次minor GC之后进入老年代,此参数只有在Serial 串行GC时有效。-Xms: 堆初始内存大小,默认为物理内存的1/64,等价于  -XX:InitialHeapSize
-Xmx: 堆最大内存大小,默认为物理内存的1/4, 等价于 -XX:MaxHeapSize
-Xmn: 设置年轻代的大小,默认为堆内存的1/3  等价于 -XX:NewSize=n
-XX:SurvivorRatio=3:  Eden/ Survivor=3    (PS:年轻代有2Survivor)
-XX:NewRatio=3:     年轻代Young / 老年代Old=3

案例一:查看某个程序配置项信息:jinfo -flag 配置项 进程号

在这里插入图片描述

查看某个程序所有配置项信息:jinfo -flags 进程号

在这里插入图片描述

案例二:查看本机MetaspaceSize大小,并修改为256MB

1、jps -l 查询后台运行的进程号码

2、jinfo -flag MetaspaceSize 1030 指定进程号的配置项信息

在这里插入图片描述

3、修改
在这里插入图片描述

4、再次测试(修改成功)

在这里插入图片描述

查看系统默认参数
第一种:
1、jps -l                         查看本机后台java进程信息
2、jinfo -flag 配置项 进程号        查看某个进程配置项相关信息
3、jinfo -flags 进程号             查看某个进程所有配置项信息第二种:
1Java -XX:+PrintFlagsInitial       查看JVM所有初始化参数
2Java -XX:+PrintFlagsFinal         查看JVM所有修改、更新后的参数= JVM默认加载     := 用户修改的
3、java -XX:+PrintFlagsFinal -XX MetaspaceSize=512m  T   运行时参数修改
4、java -XX:+PrintCommandLineFlags -version    查看内存空间初始值,初始垃圾回收器

在这里插入图片描述

总结:
查看初始垃圾回收器jinfo -flags进程号java -XX:+PrintCommandLineFlags -version 查看某个初始配置项参数值jps -l                         查看本机后台java进程信息jinfo -flag 配置项 进程号         查看某个进程配置项相关信息

4.3 垃圾收集算法与垃圾回收器

四大垃圾回收算法:引用计数法、复制算法、标记清除、标记整理
落地实现:串行Serial、并行Parallel、并发CMS、G1

七大垃圾回收器:UseSerialGC、UseParNewGC、UseParallelGC –> 新生代
UseSerialOldGC、UseConcMarkSweepGC、UseParallelOldGC、UseG1GC ->老年代

在这里插入图片描述

case1:查看本机使用的垃圾收集器(java -XX:+PrintCommandLineFlags -version)case2:配置串行Serial收集器   (年轻代:UseSerialGC串行     老年代:UseSerialOldGC串行)-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGCcase3:配置并行ParNew收集器:不推荐使用,后老年代使用CMS(年轻代:UseParNewGC并行      老年代:UseSerialOldGC串行) -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGCcase4:配置并行Parallel收集器(年轻代:UseParallelGC并行      老年代:UseParallelOldGC并行) -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGCcase5:配置并发CMS收集器(年轻代:UseParNewGC并行      老年代:UseConcMarkSweepGC并行) -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGCcase6:配置G1集器-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC怎么选择垃圾收集器?
1.优先调整堆的大小让服务器自己来选择
2.如果单核CPU或小内存(<100m), 使用串行收集器SerialSerial old
3.如果多核CPU需要最大吞吐量(如后台计算型引用) 使用并行收集器ParallelParallelold
4.如果多核CPU需要低停顿时间(如需快速响应的电商) 使用并发收集器CMSParNew
官方推荐G1,性能高。

4.4 Linux系统变慢如何定位

(整机->内存->磁盘->CPU->IO)
a、查看整体机器性能:             top   分析过程:(cpu/mem->id->load avg)1、先整体查看cpu 与mem内存占用率:是否存在某个线程占用的cpu或内存太高。2、再单个CPU占用率:因为现在都是多核cpu,按数字1,着重查看id(idle) cpu空闲率,id越大越好3、最后查看load average系统负载率:分别是1分钟,5分钟,15分钟的系统平均负载率,load average:2.33,2.51,2.12 ,如果(a+b+c)/3>6,说明系统负载率太高。b、查看内存信息                 free -mfreefree     -gfree     -m(以MB字节查看内存信息)c、查看磁盘信息                  df  -h
d、查看包含但不限于CPU也有内存 		vmstat   -n  2   3
e、查看磁盘IO信息                iostat    -xdk 2   3

在这里插入图片描述

4.5 Github骚操作

in、stars、forks、#L、location、language

1、通过in关键词限制搜索范围	java in:name,description,readme 查找项目名name、描述description、readme中包含关键字java的github项目2、通过 stars 或者forks数 去查找项目java stars:2000..5000 forks:20..50     查找点赞数stars在[2000,5000],收藏fork数[20,50]的java项目java stars:>5000 forks:>20          查找点赞数stars大于5000,收藏fork数大于20的java项目
综合:springboot项目 in name,description stars:>5000 forks:>20 pushed:>2020-01-01 3、awesome 关键字awesome java                  查找关于java的教程精品4、分享项目中某一行的代码
https://github.com/MrZhu1/solidity/blob/master/10-test_Array.sol#L23 高亮显示github关键代码的行号    #L23
https://github.com/MrZhu1/solidity/blob/master/10-test_Array.sol#L23-L27高亮显示github关键代码的段落    #L23-L275、搜索某个地区内的大佬
location:nanjing language:java       查找活跃用户

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/738795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【恒源智享云】conda虚拟环境的操作指令

conda虚拟环境的操作指令 由于虚拟环境经常会用到&#xff0c;但是我总忘记&#xff0c;所以写个博客&#xff0c;留作自用。 在恒源智享云上&#xff0c;可以直接在终端界面输入指令&#xff0c;例如&#xff1a; 查看已经存在的虚拟环境列表 conda env list查看当前虚拟…

C语言 编译和链接

1. 翻译环境和运⾏环境 在ANSI?C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令。 第2种是执⾏环境&#xff0c;它⽤于实际执⾏代码。 我们来看一下过程&#xff1a; 2. 翻译环境 那翻译环境是…

《幻兽帕鲁》新手入门 幻兽帕鲁新手开荒攻略 幻兽帕鲁配置要求

2024年1月&#xff0c;讨论热度最高的新游无疑是Pocketpair出品的《幻兽帕鲁》。这部作品发售两周即在Steam游戏平台售出超过1200万份&#xff0c;且Xbox/XGP玩家规模超过700万。不仅如此&#xff0c;该游戏同时在线玩家人数超过200万&#xff0c;在Steam的游戏史上仅次于《绝地…

java中几种对象存储(文件存储)中间件的介绍

一、前言 在博主得到系统中使用的对象存储主要有OSS&#xff08;阿里云的对象存储&#xff09; COS&#xff08;腾讯云的对象存储&#xff09;OBS&#xff08;华为云的对象存储&#xff09;还有就是MinIO 这些玩意。其实这种东西大差不差&#xff0c;几乎实现方式都是一样&…

深入理解Java中的ConcurrentSkipListMap:高效并发的有序映射

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 摘要&#xff1a;本文将详细介绍Java中的ConcurrentSkipListMap&#xff0c;一个支持高效并发操作的有序映射。我们将深入探讨其数…

xilinx SDK 2018.3 undefined reference to `f_mount‘,`f_open‘等等

用xilinx SDK 写SD的读写实验时&#xff0c;已经添加了头文件ff.h并且没有报错&#xff0c;但是当用到内部的函数f_mount&#xff0c;f_open’等等时却显示未定义。 很可能是漏掉了在ZYNQ中定义SD的MIO接口&#xff0c;在下方图示中进行定义&#xff08;需要查找自己板子的原理…

Java零基础入门到精通_Day 1

01 Java 语言发展史 Java语言是美国Sun公司(StanfordUniversity Network)在1995年推出的 计算机语言Java之父:詹姆斯高斯林(ames Gosling) 重要的版本过度&#xff1a; 2004年 Java 5.0 2014年 Java 8.0 2018年 9月 Java 11.0 &#xff08;目前所使用的&#xff09; 02 J…

函数的说明文档

函数是纯代码语言&#xff0c;想要理解其含义&#xff0c;就需要一行行去阅读理解代码&#xff0c;效率比较低。 我们可以给函数添加说明文档&#xff0c;辅助理解函数的作用。 语法如下&#xff1a; 通过多行注释的形式&#xff0c;对函数进行说明解释 内容应写在函数体之前…

leetcode 25、k个一组翻转链表

给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内部的值…

SPEL表达式及注入漏洞

SPEL,全称为Spring表达式语言&#xff0c;是一个由 Spring 框架提供的表达式语言。它是一种基于字符串的表达式语言&#xff0c;可以在运行时对对象进行查询和操作。 SpEL 支持在XML和注解配置中使用&#xff0c;它可以在Spring框架的各种组件中使用&#xff0c;如Spring MVC …

springboot261高校专业实习管理系统的设计和开发

基于spring boot的高校专业实习管理系统的设计与实现 摘 要 随着国内市场经济这几十年来的蓬勃发展&#xff0c;突然遇到了从国外传入国内的互联网技术&#xff0c;互联网产业从开始的群众不信任&#xff0c;到现在的离不开&#xff0c;中间经历了很多挫折。本次开发的高校专…

制造行业大数据应用:四大领域驱动产业升级与智慧发展

一、大数据应用&#xff1a;制造行业的智慧引擎 随着大数据技术的不断突破与普及&#xff0c;制造行业正迎来一场前所未有的变革。大数据应用&#xff0c;如同智慧引擎一般&#xff0c;为制造行业注入了新的活力&#xff0c;推动了产业升级与创新发展。 二、大数据应用在制造行…

外贸人要加油努力,到底怎么做

我们说要加油&#xff0c;要努力&#xff0c;那做外贸我们的力气到底应该往哪里使&#xff1f;想要把外贸做好容易吗&#xff1f; 其实没有一件事情背后他是真正容易的&#xff0c;如果发现自己迷茫了&#xff0c;很有可能是你既要又要&#xff0c;没有自己的一个满足感&#…

Elasticsearch:机器学习与人工智能 - 理解差异

作者&#xff1a;来自 Elastic Aditya Tripathi, Jessica Taylor 长期以来&#xff0c;人工智能几乎完全是科幻小说作家的玩物&#xff0c;人类将技术推得太远&#xff0c;以至于它变得活跃起来 —— 正如好莱坞让我们相信的那样 —— 开始造成严重破坏。 令人愉快的东西&#…

C++中的RAII原则和资源管理如何提高程序效率和安全性?

文章目录 C中的RAII&#xff08;Resource Acquisition Is Initialization&#xff09;原则是一种编程范式&#xff0c;它确保资源在其生命周期内的有效管理。RAII的核心思想是在对象创建时&#xff08;初始化阶段&#xff09;获取资源&#xff0c;并在对象销毁时&#xff08;析…

论企业安全漏洞扫描的重要性

前言 随着信息技术的迅猛发展和互联网的广泛普及&#xff0c;网络安全问题日益凸显。在这个数字化的世界里&#xff0c;无论是企业还是个人&#xff0c;都面临着前所未有的安全威胁。安全漏洞&#xff0c;作为这些威胁的源头&#xff0c;常常被忽视或无法及时发现。 而安全漏洞…

ansible-playbook的角色(role)

1前言 角色目录如下&#xff08;分别为httpd角色和nginx角色&#xff09; handlers/ &#xff1a;至少应该包含一个名为 main.yml 的文件&#xff1b; 其它的文件需要在此文件中通过include 进行包含 vars/ &#xff1a;定义变量&#xff0c;至少应该包含一个名为 main.yml 的…

JDK环境变量配置-jre\bin、rt.jar、dt.jar、tools.jar

我们主要看下rt.jar、dt.jar、tools.jar的作用&#xff0c;rt.jar在​%JAVA_HOME%\jre\lib&#xff0c;dt.jar和tools.jar在%JAVA_HOME%\lib下。 rt.jar&#xff1a;Java基础类库&#xff0c;也就是Java doc里面看到的所有的类的class文件。 tools.jar&#xff1a;是系统用来编…

在ubuntu上安装FastSufer【本机安装】

亲测:FastSurfer分割并重建一个大脑需要1个小时,而freeSurfer需要8个小时。确实很快! 这里我在网页端搭建了一个小的工具包,里面集成了经典的freeSurfer和较快的FastSurfer。如果你不想安装或者手头没有linux设备,您也可以直接从以下网址直接使用,跳过繁琐的安装步骤!!…

嵌入式面经-ARM体系架构-寄存器与异常处理

ARM寄存器组织 寄存器概念 寄存器是处理器内部的存储器&#xff0c;没有地址 寄存器作用 一般用于暂时存放参与运算的数据和运算结果 在某个特定模式下只能使用当前模式下的寄存器&#xff0c;一个模式下特有的寄存器别的模式下不能使用 一共是40个寄存器 寄存器分类 通用寄…