my面试

这里写自定义目录标题

  • 面试大全 https://blog.csdn.net/weixin_43495390/article/details/86533482

1.集合

  • 一类是继承自Collection接口,这类集合包含List、Set和Queue等集合类。另一类是继承自Map接口,这主要包含了哈希表相关的集合类。

一.List和Set

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lQWd1yp4-1585889807453)(…/images/image-20191225161348971.png)]

图中的绿色的虚线代表实现,绿色实线代表接口之间的继承,蓝色实线代表类之间的继承。

list 元素 有序可重复			add
set  元素 无序不可重复
— List 有序,可重复

    ArrayList
    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程不安全,效率高
    Vector
    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程安全,效率低
    LinkedList
    优点: 底层数据结构是链表,查询慢,增删快。
    缺点: 线程不安全,效率高

—Set 无序,唯一

    HashSet---底层采用 HashMap 来保存元素
    底层数据结构是哈希表。(无序,唯一)
    --相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,我们应该为保存到 HashSet 中的对象覆盖 hashCode() 和 equals()。
    如何来保证元素唯一性?
    1.依赖两个方法:hashCode()和equals()

    LinkedHashSet
    底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
    1.由链表保证元素有序
    2.由哈希表保证元素唯一

    TreeSet
    底层数据结构是红黑树。(唯一,有序)
    1. 如何保证元素排序的呢?
    自然排序
    比较器排序
    2.如何保证元素唯一性的呢?
    根据比较的返回值是否是0来决定
————————————————
版权声明:本文为CSDN博主「游走的大千世界的烤腰子」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhangqunshuai/article/details/80660974

1.常用的ArrayList,LinkedList,TreeSet,HashSet。

  • ArrayList,LinkedList的区别?
相同点 都是实现了List接口--都可以存储任意类型多个数据。

1.ArrayList 底层是基于数组实现的,具有查询快的特点。因为可以通过索引直接定位到数据,查询效率高。添加数据慢,数组长度确定后不能修改,如果扩容 会重新创建数组,复制数据,损耗性能。

2.LinkedList 底层基于链表实现的,具有添加快的特点。 因为添加的时候直接创建一个节点就行,不会涉及扩容,效率高。查询较慢,需遍历链表,效率低。

3.LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
  • TreeSet,HashSet的区别?
相同点:都是set接口实现类 都可以存储任意类型任意多个数据。

1.TreeSet 此类保证排序后的 set 按照升序排列元素,根据使用的构造方法不同,可能会按照元素的自然顺序 进行排序,或按照在创建 set 时所提供的比较器进行排序。是一个有序集合,元素中按升序排序,缺省是按照自然顺序进行排序,意味着TreeSet中元素要实现Comparable接口;我们可以构造TreeSet对象时,传递实现了Comparator接口的比较器对象.

1.TreeSet 可以对Set集合中的元素进行排序,是线程不安全的。
	唯一性(去重):根据比较方法的返回结果是否为0,如果是0,则是相同元素,不保存,如果不是0,则是不同元素,存储。

	TreeSet对元素进行排序的方式(2种):
-----元素自身具备比较功能(自然排序),需要实现Comparable接口,并覆盖其compareTo方法。

-----元素自身不具备比较功能(定制排序),则需要实现Comparator接口,并覆盖其compare方法。

2.HashSet 内部的数据结构是哈希表,是线程不安全的。
唯一性(去重):通过对象的hashCode和equals方法来完成对象唯一性的判断。  添加时,用equals判断hashCode值是否相同,不同就保存。
注意:如果元素要存储到HashCode中,必须覆盖hashCode方法和equals方法。
  • 总结:ComparaTo()方法和Comparator()方法的区别:
ComparaTo()方法只需要传递一个参数对象,与本类对象进行比较。它是元素的比较方法。

Comparator()方法是集合的比较方法。强行对某个对象 collection 进行整体排序 的比较函数

二.Map

那么哈希冲突如何解决呢?  计算哈希值时,得到了同一个地址
	哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。

1.常用的 HashMap-不安全,Hashtable-安全,ConcurrentHashMap-安全,Properties

  • 三者之间的区别
相同点:都是map接口实现 都是以键值对形式存储值

HashMap 线程不安全  可以以null作为键或值 效率较高
Hashtable 线程安全 不可以以null作为键或值 效率较低
ConcurrentHashMap 线程安全 不可以以null作为键或值 效率相对较高
Properties它是继承了Hashtable 类,以Map 的形式进行放置值, put(key,value) get(key)。  key不能重复
	load加载方法    store 保存方法

HashMap底层put方法

在jdk1.8之后,是采用数组+链表+红黑树的结构  
长度:1.6之前初始化10   1.8之后长度为0

当调用put方法时,会创建一个数组,根据key的HashCode,计算出hash值(return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);),然后再根据该hash值得到这个元素在数组中的位置(得到该hash值所对应table中索引的方法:int i = indexFor(hash,table.length))(即下标)。
用equals判断key,如果该位置上没有元素,就直接将该元素放到此数组中的该位置上,判断当前链表长度是否>=7,满足就变成红黑树,不满足就直接添加。
如果有元素(hash冲突),判断key的hash值和要添加元素的hash值是否相等,相等就返回覆盖的值。判断当前链表长度是否>=7,满足就变成红黑树,不满足就直接添加。
最后判断是否需要扩容。16*0.75=12   超过12就自动扩容。没有就结束。

HashMap底层get方法

首先计算key的 hash值取得所定位的桶。 桶(entry)存储key-value
如果桶为空则直接返回 null 。
否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
如果第一个不匹配,则判断它的下一个是红黑树还是链表。
红黑树就按照树的查找方式返回值。
不然就按照链表的方式遍历匹配返回值。
从这两个核心方法(get/put)可以看出 1.8 中对大链表做了优化,修改为红黑树之后查询效率直接提高到了 O(logn)。

HashMap遍历

HashMap的遍历方式
主要通过获取keyset和entryset 然后使用迭代器和foreach

HashMap  是以键值对形式存储值 Entry

ConcurrentHashMap-线程安全

其实和 1.8 HashMap 结构类似,当链表节点数超过指定阈值的话,也是会转换成红黑树的,大体结构也是一样的。
那么它到底是如何实现线程安全的?
答案:其中抛弃了原有的Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

put过程:
1、判断Node[]数组是否初始化,没有则进行初始化操作
2、通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下次循环。
3、检查到内部正在扩容,就帮助它一块扩容。
4、如果f!=null,则使用synchronized锁住f元素(链表/红黑树的头元素)。如果是Node(链表结构)则执行链表的添加操作;如果是TreeNode(树型结构)则执行树添加操作。
5、判断链表长度已经达到临界值8(默认值),当节点超过这个值就需要把链表转换为树结构。
6、如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容
https://www.jianshu.com/p/5dbaa6707017

hashtable

HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占,相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下。ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

2.线程

一.线程的创建3种方式

(1)继承Thread类
	需要实现 run() 方法,并且最后也是调用 start() 方法来启动线程
(2)实现Runnable接口
	实现 run() 方法。通过 Thread 调用 start() 方法来启动线程。
(3)实现Callable接口----使用ExecutorService、Callable、Future实现有返回结果的多线程
	与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
  • 实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
实现接口 VS 继承 Thread

单继承,多实现。

实现接口会更好一些,因为:
Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
类可能只要求可执行即可,继承整个 Thread 类开销会过大。
1.2 Thread和Runable的区别和联系
(1)联系:
1、Thread类实现了Runable接口。
2、都需要重写里面Run方法。
(2)不同:
1、实现Runnable的类更具有健壮性,避免了单继承的局限。
2、Runnable更容易实现资源共享,能多个线程同时处理一个资源

1.线程的状态有哪些(生命周期) 线程中的方法有哪些?

状态5种
1.新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
2.就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
3.运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
4.死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

5.阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

	等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

	同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

	其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

-------------------------------------------------------

方法
start() 启动线程并执行相应的run()方法
run()   子线程要执行的代码放入run()方法

yield方法:使当前线程从执行状态变为就绪状态。
sleep方法:强制当前正在执行的线程休眠,当睡眠时间到期,则返回到可运行状态。 不会放弃锁资源
join方法:通常用于在main()主线程内,等待其它线程完成再结束main()主线程,不会放弃锁资源
deamon:守护线程(deamon)是程序运行时在后台提供服务的线程,并不属于程序中不可或缺的部分。
当所有非后台线程结束时,程序也就终止,同时会杀死所有后台线程。
main() 属于非后台线程。

使用 setDaemon() 方法将一个线程设置为后台线程。

2.sleep和wait的区别

Thread.sleep() --方法进入休眠状态,不会放弃锁资源
wait() ---Object中方法。 会放弃锁资源 ,使线程挂起,直到线程得到 notify() 或 notifyAll() 消息(或者 java.util.concurrent 类库中等价的 signal() 或 signalAll() 消息;

3.线程安全

  • 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的

线程不安全----当多个线程访问同一个资源出现问题。

3.如何解决线程安全问题?

- 互斥同步	悲观锁 synchronized 关键字
	同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个(或者是一些,使用信号量的时候)线程使用。synchronized  jvm管理的 可以锁代码块,方法,类。必须上一个线程执行完才能执行下一个线程。

特点:同步方法  1.效率会变低
	         2.没有线程安全问题了
特点:同步代码块 
			 1. 把业务代码 放在 同步代码块中
			 2. 该代码块中的代码在一个时间点只能被一个线程访问,其他线程需要排队等待

- 非阻塞同步	乐观锁实现
 * 使用Lock的步骤:
 * 1.创建Lock实现类的对象
 * 2.使用Lock对象的lock方法加锁
 * 3.使用Lock对象的unlock方法解锁
 * 注意:可把unlock方法的调用放在finally代码块中,保证一定能解锁
 
 
Lock  乐观锁   线程安全 效率高

	CAS   compare  and swap 取出来先比较
	出现:ABA 问题      加一个版本比较 version
 ①创建ReentrantLock对象
     ②调用lock方法:加锁
         {代码....}
     ③调用unlock方法:解锁
      注意:可把解锁的unlock方法的调用放在finally{}代码块中,保证一定能解锁

4.synchronize悲观锁与lock乐观锁的区别?

  • 同步方法 一个方法只有一个线程执行,必须上一个线程执行完才能执行下一个线程。

  • 同步代码块 : synchronized(被加锁的对象){ 代码 }


RandomAccess

Vector 线程安全

在相对于ArrayList来说,Vector线程是安全的,也就是说是同步的。创建了一个向量类的对象后,可以往其中随意地插入不同的类的对象,既不需顾及类型也不需预先选定向量的容量,并可方便地进行查找。对于预先不知或不愿预先定义数组大小,并需频繁进行查找、插入和删除工作的情况,可以考虑使用向量类。向量类提供了三种构造方法:
public Vector()
public Vector(int initialcapacity,int capacityIncrement)
public Vector(int initialcapacity)

死锁

  • 同步中嵌套同步,锁没有来得及释放,一直等待,就导致死锁。

  • 如果线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。

下面这段代码,多运行几次就会出现死锁,思路是开启两个线程,让这两个线程执行的代码获取的锁的顺序不同,第一个线程需要先获得obj对象锁,然后再获得this锁,才可以执行代码,然后释放两把锁。线程2需要先获得this锁,再获取obj对象锁才可执行代码,然后释放两把锁。但是,当线程1获得了obj锁之后,线程2获得了this锁,这时候线程1需要获得this锁才可执行,但是线程2也无法获取到obj对象锁执行代码并释放,所以两个线程都拿着一把锁不释放,这就产生了死锁。

多线程三大特性

原子性
原子性就是在执行一个或者多个操作的过程中,要么全部执行完不被任何因素打断,要么不执行。比如银行转账,A账户减去100元,B账户必须增加100元,对这两个账户的操作必须保证原子性,才不会出现问题。还有比如:i=i+1的操作,需要先取出i,然后对i进行+1操作,然后再给i赋值,这个式子就不是原子性的,需要同步来实现数据的安全。

原子性就是为了保证数据一致,线程安全。
----------------------
可见性
当多个线程访问同一个变量时,一个线程修改了变量的值,其他的线程能立即看到,这就是可见性。

这里讲一下Java内存模型?简称JMM,决定了一个线程与另一个线程是否可见,包括主内存(存放共享的全局变量)和私有本地内存(存放本地线程私有变量)

本地私有内存存放的是共享变量的副本,线程操作共享变量,首先操作的是自己本地内存的副本,当同一时刻只有一个线程操作共享变量时,该线程操作完毕本地内存,然后会刷新到主内存,然后主内存会通知另一个线程,进而更新;但是如果同一时刻有多个线程操作共享变量,会来不及更新主内存进而通知其他线程更新变量,就会出现冲突问题。
---------------
有序性
就是程序的执行顺序会按照代码先后顺序进行执行,一般情况下,处理器由于要提高执行效率,对代码进行重排序,运行的顺序可能和代码先后顺序不同,但是结果一样。单线程下不会出现问题,多线程就会出现问题了。

volatile

保证可见性,但是不保证原子性。

3.线程池

  • 定义:线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

1.线程池的作用及原理

1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率。(线程复用)
2.线程并发数量过多,抢占系统资源从而导致阻塞。(控制并发数量)
3.对线程进行一些简单的管理。(管理线程的生命周期)

线程池原理

1.线程复用:实现线程复用的原理应该就是要保持线程处于存活状态(就绪,运行或阻塞)
2.控制并发数量:(核心线程和最大线程数控制)
3.管理线程(设置线程的状态)

线程池的返回值ExecutorService简介

 ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程

2.常见四种线程池

1.CachedThreadPool() 可缓存线程池

    /**
     根据源码可以看出:
    这种线程池内部没有核心线程,线程的数量是有限制的 最大是Integer最大值。
    在创建任务时,若有空闲的线程时则复用空闲的线程,若没有则新建线程。
    没有工作的线程(闲置状态)在超过了60S还不做事,就会销毁。
    适用:执行很多短期异步的小程序或者负载较轻的服务器。
    */
public class Test1 {
	
	public static ExecutorService newCachedThreadPool (){
		return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
	}
}

2.FixedThreadPool(int nThreads) 定长线程池

/**
 根据源码可以看出:
该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁。
如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。
适用:执行长期的任务,性能好很多。
*/L
public class Test1 {
	//传入线程长度写死
	public static ExecutorService newFixedThreadPool (int nThreads){
		return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
	}
}

3.SingleThreadPool() 单线程池

/**
根据源码可以看出:
该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁。
如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。
适用:执行长期的任务,性能好很多。
*/
public class Test1 {
	//传入线程长度写死
	public static ExecutorService newFixedThreadPool (){
		return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
	}
}

4.ScheduledThreadPool() 弹性缓存线程池

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FdQLdVt-1585889807457)(…/images/image-20191226165626552.png)]

根据源码可以看出:
DEFAULT_KEEPALIVE_MILLIS就是默认10L,这里就是10秒。这个线程池有点像是CachedThreadPool和FixedThreadPool 结合了一下。
不仅设置了核心线程数,最大线程数也是Integer.MAX_VALUE。
这个线程池是上述4个中唯一一个有延迟执行和周期执行任务的线程池。
适用:周期性执行任务的场景(定期的同步数据)
总结:除了new ScheduledThreadPool 的内部实现特殊一点之外,其它线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。

5.自定义线程池 ThreadPoolExecutor 类构造器语法形式

ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,timeUnit,workQueue,threadFactory,handle);   
方法参数:
corePoolSize:核心线程数(最小存活的工作线程数量)
maxPoolSize:最大线程数
keepAliveTime:线程存活时间(在corePoreSize<maxPoolSize情况下有用,线程的空闲时间超过keepAliveTime就会销毁)
timeUnit:存活时间的时间单位
workQueue:阻塞队列,用来保存等待被执行的任务(①synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务;②LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;③ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小)
threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略(①丢弃任务并抛出RejectedExecutionException异常;②丢弃任务,但是不抛出异常;③丢弃队列最前面的任务,然后重新尝试执行任务;④由调用线程处理该任务)

6.ThreadPoolExecutor常见的方法

execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,实际上它还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。

shutdown()不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
shutdownNow()立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

isTerminated()方法
调用ExecutorService.shutdown方法的时候,线程池不再接收任何新任务,但此时线程池并不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。在调用shutdown方法后我们可以在一个死循环里面用isTerminated方法判断是否线程池中的所有线程已经执行完毕,如果子线程都结束了,我们就可以做关闭流等后续操作了。

7.线程池中的最大线程数

一般说来,线程池的大小经验值应该这样设置:(其中N为CPU的个数)

如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为2N+1
如果一台服务器上只部署这一个应用并且只有这一个线程池,那么这种估算或许合理,具体还需自行测试验证。
但是,IO优化中,这样的估算公式可能更适合:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
因为很显然,线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
创建线程的个数是还要考虑 内存资源是否足够装下相当的线程
下面举个例子:
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。

8.四种线程池拒绝策略

  • https://www.jianshu.com/p/a55da1c8bb93

  • 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
	这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
2.ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
	如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略(博客网站统计阅读量就是采用的这种拒绝策略)。
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
	此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
	如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务,我们可以通过代码来验证这一点
---通过结果可以看到,主线程main也执行了任务,这正说明了此拒绝策略由调用线程(提交任务的线程)直接执行被丢弃的任务的。

4.JVM

  • JVM是java虚拟机,在不同操作系统安装不同版本的jvm,来运行编译过后的字节码文件(.class)来实现跨平台原理。

为什么要优化?

  • 采用默认的配置不见得能起到最好的效果,甚至可能会导致运行效率更差,又或者面临高并发情况下,想让程序平稳顺畅的运行,所以我们需要针对实际的需要来进行优化.

插件 VisualVM

现在安装插件,插件的安装属于VisualVM的一个重要功能,凭借插件我们可以将这个工具的功能变得更强大。
打开工具->插件
选择“可用插件”页
我们在这里安装一个Visual GC,方便我们看到内存回收以及各个分代的情况
打上勾之后点击安装,就是常规的next以及同意协议等
网络不是很稳定,有时候可能需要多尝试几次。
安装完成后我们将当前监控页关掉,再次打开,就可以看到Profiler后面多了一个Visual GC页。
  • 另外,如果开发工具使用的是Intellij IDEA的话,可以下载一个插件,VisualVM Launcher,通过插件启动可以直接到上述页面,不用在左边的条目中寻找自己的项目.

1.JVM组成

  • 每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。
1.类加载器子系统
2.运行时数据区
    方法区 堆  虚拟机栈 本地方法栈 程序计数器
3.执行引擎
4.本地方法库
	而本地库接口也就是用于调用本地方法的接口,在此我们不细说,主要关注的是上述的4个组件

2.类加载器子系统执行过程

加载,验证,准备,解析和初始化这5个步骤.
1.加载:找到字节码文件,读取到内存中.类的加载方式分为隐式加载和显示加载两种。隐式加载指的是程序在使用new关键词创建对象时,会调用类的加载器把对应的类加载到jvm中。显示加载指的是通过直接调用class.forName()方法来把所需的类加载到jvm中。(将字节码加载到内存中)
2.验证:验证此字节码文件是不是真的是一个字节码文件,毕竟后缀名可以随便改,而内在的身份标识是不会变的.在确认是一个字节码文件后,还会检查一系列的是否可运行验证,元数据验证,字节码验证,符号引用验证等.Java虚拟机规范对此要求很严格,在Java 7的规范中,已经有130页的描述验证过程的内容.
3.准备:为类中static修饰的变量分配内存空间并设置其初始值为0或null.可能会有人感觉奇怪,在类中定义一个static修饰的int,并赋值了123,为什么这里还是赋值0.因为这个int的123是在初始化阶段的时候才赋值的,这里只是先把内存分配好.但如果你的static修饰还加上了final,那么就会在准备阶段就会赋值.
4.解析:解析阶段会将java代码中的符号引用替换为直接引用.比如引用的是一个类,我们在代码中只有全限定名来标识它,在这个阶段会找到这个类加载到内存中的地址.
5.初始化:如刚才准备阶段所说的,这个阶段就是对变量的赋值的阶段.

原文链接:https://blog.csdn.net/qq_41701956/article/details/80020103

一、JVM将整个类加载过程划分为了三个步骤:
(1)装载(加载)
      装载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载,同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID,因此不同类加载器加载相同的类是不同的。有继承,将先在加载父类。

  将类.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区中的数据结构

(2)链接
      链接过程负责对二进制字节码的格式进行:校验、解析类中调用的接口、类。校验是防止不合法的.class文件,然后 对类中的所有属性、调用方法进行解析,以确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等),会造成NoSuchMethodError、NoSuchFieldError等错误信息。

  连接:
    a、验证:确保被加载的类的正确性

    b、准备:为类的静态变量分配内存,并将其初始化为默认值

    c、解析: 把类中的符号引用转化为直接引用

(3)初始化
      初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化。

  在四种情况下初始化过程会被触发执行:

      调用了new;

  反射调用了类中的方法;

  子类调用了初始化(先执行父类静态代码和静态成员,再执行子类静态代码和静态变量,然后调用父类构造器,最后调用自身构造器。);

  JVM启动过程中指定的初始化类。

3.双亲委派机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gGFVXnmP-1585889807459)(…/images/image-20191226184500324.png)]

类加载器一般有4种,其中前3种是必然存在的 
1.启动类加载器:加载<JAVA_HOME>\lib下的   		C++实现的
2.扩展类加载器:加载<JAVA_HOME>\lib\ext下的	JVM用此classloader来加载扩展功能的一些jar包
3.应用程序类加载器:	加载Classpath下的		   
4.自定义类加载器

而双亲委派机制是如何运作的呢?
	我们以应用程序类加载器举例,它在需要加载一个类的时候,不会直接去尝试加载,而是委托上级的扩展类加载器去加载,而扩展类加载器也是委托启动类加载器去加载.
启动类加载器在自己的搜索范围内没有找到这么一个类,表示自己无法加载,就再让扩展类加载器去加载,同样的,扩展类加载器在自己的搜索范围内找一遍,如果还是没有找到,就委托应用程序类加载器去加载.如果最终还是没找到,那就会直接抛出异常了.
而为什么要这么麻烦的从下到上,再从上到下呢?
这是为了安全着想,保证按照优先级加载.如果用户自己编写一个名为java.lang.Object的类,放到自己的Classpath中,没有这种优先级保证,应用程序类加载器就把这个当做Object加载到了内存中,从而会引发一片混乱.而凭借这种双亲委派机制,先一路向上委托,启动类加载器去找的时候,就把正确的Object加载到了内存中,后面再加载自行编写的Object的时候,是不会加载运行的.
  • 注意:

    1、所有的类加载器都继承与ClassLoader类。并且BootstrapClassLoader、ExtClassLoader、AppClassLoader继承于Java.net.URLClassLoader,可以从本地和网上下载字节码。URLClassLoader继承ClassLoader。;

    2、BootstrapClassLoader是最先加载的,它然后依次加载出ExtClassLoader、AppClassLoader对象。

4.运行时数据区

  • 运行时数据区分为 虚拟机栈, 本地方法栈, 堆区, 方法区 和 程序计数器.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNgKMca2-1585889807460)(…/images/image-20191226185003740.png)]

  • 1.方法区域(Method Area)
博客
在Sun JDK中这块区域对应的为PermanetGeneration,又称
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值