并发和锁
(1)synchronized在JDK6做了哪些优化
1.适应自旋锁:
自旋锁:为了减少线程状态改变带来的消耗 不停地执行当前线程
2.锁消除:
不可能存在共享数据竞争的锁进行消除
3.锁粗化:
将连续的加锁 精简到只加一次锁
4.轻量级锁:
无竞争条件下 通过CAS消除同步互斥
5.偏向锁:
无竞争条件下 消除整个同步互斥,连CAS都不操作。
Java并发编程:Lock
转载 http://www.cnblogs.com/dolphin0520/p/3923167.html
synchronized的缺陷
synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
synchronized和lock的区别
1.Lock是个接口,而synchronized是java关键字,synchronized是内置语言实现
2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象的发生;而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很有可能造成死锁现象,因此使用Lock时需要在finally块中释放锁
3.Lock可以让等待锁的线程相应中断;而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
4.通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
5.Lock可以提高多个线程读操作的效率
在性能上来说,如果资源竞争不激烈的话,两者的性能是差不多的;而当资源竞争非常激烈(即有大量线程同时竞争)时,Lock的性能要远远优于synchronized
锁的相关概念介绍
1.可重入锁
假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
2.可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
lockInterruptibly()的用法时已经体现了Lock的可中断性。
3.公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
4.读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
可以通过readLock()获取读锁,通过writeLock()获取写锁。
(2)懒汉单例用duble check是线程安全的么,为什么要加volatile
单例模式与线程安全(懒汉式、double check、volatile)
https://blog.csdn.net/qq_41911762/article/details/102806837
1.饿汉式单例
饿汉式单例:在类加载的时候,就已经初始化,无论之后用没用到。这样写法简单,线程安全,但是占内存。值得注意的是构造方法必须私有。
package Singleton;
public class HungerSingleton {
private static HungerSingleton singleton=new HungerSingleton();
private HungerSingleton(){
}
public static HungerSingleton getInstance(){
return singleton;
}
public static void main(String[]args){
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(HungerSingleton.getInstance());
}
}).start();
}
}
}
/*
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
Singleton.HungerSingleton@51d1628b
*/
2.懒汉式单例
(1)比较简单的一种写法
与饿汉式单例不同的是,懒汉式单例是要在用到的时候才实例化,而且要考虑线程安全问题,多个线程在获取实例的时候需要对获取实例的方法加锁。如果不加锁,获取到的很可能不是同一个对象(当singleton未使用时(为null),多个线程获取实例时都判断singleton==null为真,都new了一个对象)。
(2)上述的写法是比较消耗性能的,如果单例已经实例化了,那么多个线程在获取实例时,只能有一个线程能获取到锁,调用getInstance()方法,其他线程会被阻塞在外边。
为了不让线程在方法外等待,而是实例不为空就直接返回,可以增加 synchronized (LazySingleton.class)
但是发现得到的并不是同一个对象,这是因为当一个线程进入synchronize中的代码块时,还没来得及实例化,另一个线程判断(singleton==null)进入if语句,但是没获取到锁,在外面等待,当前一个线程实例化完后,释放了锁,后一个线程拿到锁继续执行,又实例化了一个对象。所以这样写不是线程安全的。
(3)由于上面的原因,衍生出“双重检查”的写法
(4)但是 在jvm中,new操作并不是原子性的,一个new语句包括了:
1.给instance分配空间
2.调用 Singleton 的构造函数来初始化
3.将instance对象指向分配的内存空间
在JVM中的及时编译存在指令重排序的优化,也就是说,上述执行顺序不能保证,如果第一个线程在实例化对象时,2执行在3前边,当线程执行完2,singleton就已经非空了,如果其他线程在第一个线程为执行完3之前,调用getInstance()方法将获取到一个不完整的对象(未初始化),使用则会报错。这个演示不了。。
所以需要volatile关键字修饰singeton,禁止JVM进行指令重排序。(这是最正确的写法,要记住)
package Singleton;
public class LazySingleton {
private static volatile LazySingleton singleton = null; //volatile关键字修饰singeton,禁止JVM进行指令重排序。
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (singleton == null) {
synchronized (LazySingleton.class) { //为了不让线程在方法外等待,而是实例不为空就直接返回
if (singleton == null) {
singleton = new LazySingleton();
}
}
}
return singleton;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(LazySingleton.getInstance());
}
}).start();
}
}
}
/*
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
Singleton.LazySingleton@1ec68e22
*/
(3)Volatile有什么用,什么是CAS
Java并发编程:volatile关键字解析
https://www.cnblogs.com/dolphin0520/p/3920373.html
可见性
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
有序性
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
1.volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
2.volatile保证原子性吗?
不能保证操作的原子性
在java 1.5的java.util.concurrent.atomic包下提供了一些原子操作类,即对基本数据类型的 自增(加1操作),自减(减1操作)、以及加法操作(加一个数),减法操作(减一个数)进行了封装,保证这些操作是原子性操作。atomic是利用CAS来实现原子性操作的(Compare And Swap),CAS实际上是利用处理器提供的CMPXCHG指令实现的,而处理器执行CMPXCHG指令是一个原子性操作。
3.volatile能保证有序性吗?
在前面提到volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
4.volatile的原理和实现机制
前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。
下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
使用volatile关键字的场景
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
1.状态标记量
2.double check
(4)什么是happens before原则
happens-before 规则
https://blog.csdn.net/liu_dong_liang/article/details/80391040
happens-before原则(先行发生原则)
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
这8条原则摘自《深入理解Java虚拟机》。
这8条规则中,前4条规则是比较重要的,后4条规则都是显而易见的。
下面我们来解释一下前4条规则:
对于程序次序规则来说,我的理解就是一段程序代码的执行在单个线程中看起来是有序的。注意,虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的,因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序。因此,在单个线程中,程序执行看起来是有序执行的,这一点要注意理解。事实上,这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。
第二条规则也比较容易理解,也就是说无论在单线程中还是多线程中,同一个锁如果出于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作。
第三条规则是一条比较重要的规则,也是后文将要重点讲述的内容。直观地解释就是,如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作。
第四条规则实际上就是体现happens-before原则具备传递性。
(5)什么是AQS
什么是AQS及其原理
https://blog.csdn.net/striveb/article/details/86761900
1、AQS简介
AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.lang.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表。
(6)线程sleep和wait的区别,线程join是什么意思
1、所属分类
sleep是Thread的方法,wait是Object的方法。
2、对象锁的控制
sleep监控状态依然保持,不会释放对象锁,不会影响其它进程对象的运行;wait释放对象锁,使其他线程可以使用同步控制块或者方法。
3、唤醒机制
sleep为静态方法,使当前线程处于睡眠状态,到设定时间自动恢复;wait除了常用的使用notify或notifyAll唤醒方式外,也有自动唤醒方式。
4、异常处理
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
5、使用场景
sleep可以在任何地方使用,wait只能在同步控制方法或同步代码块中使用,必须与synchronized和notify/notifyAll搭配使用,不然会出现明显的死锁。
synchronized(x){
x.notify()
//或者wait()
}
sleep()
1、属于Thread类,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态
2、sleep方法没有释放锁
3、sleep必须捕获异常
4、sleep可以在 任何地方使用
wait()
1、属于Object,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程
2、wait方法释放了锁
3、wait不需要捕获异常
4、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用
join方法
https://blog.csdn.net/qq_38545713/article/details/79778930
等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
(7)Java都有哪几种锁
https://blog.csdn.net/u010251897/article/details/80840027
https://blog.csdn.net/u010648018/article/details/79750608
(8)Java乐观锁的实现(CAS+自旋)
乐观锁以及乐观锁的实现
https://blog.csdn.net/sunwenhao_2017/article/details/81565783
1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
2.乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
CAS自旋
CAS(Compare And Swap)比较并转换
该算法涉及三个数:内存值V,旧的预期值A,新的预期值B。当且仅当旧的预期值A和内存值V相同时,将内存值改为B,否则什么也不做。
如何来理解上面这一段话呢?我们先了解一下乐观锁和悲观锁各自的做事方式,首先,悲观锁的态度是一件事情我必须要能百分之百掌控才能去做,否则就认为这件事情一定会出问题,而乐观锁的态度就是不管什么事情,我都会先尝试去做,大不了最后不成功就是了。
基于CAS的自旋就是典型的乐观锁,程序执行时,线程1从共享内存中取值V并建一个副本A,对A进行计算后将新的值保存为B,然后对A值和内存中的V值进行比较,如果A等于V,则认为内存中的V值没有被其他线程修改过,可以将新值B赋给内存,否则,认为内存中已被其他的线程修改,则重新执行计算操作和检测,知道旧的期望值A等于内存值V为止。
java并发包java.util.concurrent.*的核心就是CAS自旋原理。如AtomicInteger、AtomicLong等都是基于CAS实现的。
(9)阻塞队列的实现,至少自己会实现2种阻塞队列的方法(单锁,多锁, ReentrantLock, Condition)
Java并发编程-阻塞队列(BlockingQueue)的实现原理
https://blog.csdn.net/chenchaofuck1/article/details/51660119
集合和多线程
(10)进程,协程和线程的区别
进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
进程和其他两个的区别还是很明显的。
协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。
打个比方吧,假设有一个操作系统,是单核的,系统上没有其他的程序需要运行,有两个线程 A 和 B ,A 和 B 在单独运行时都需要 10 秒来完成自己的任务,而且任务都是运算操作,A B 之间也没有竞争和共享数据的问题。现在 A B 两个线程并行,操作系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共需要 20 + 19 * 0.1 = 21.9 秒。如果使用协程的方式,可以先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间是 20 + 1 * 0.1 = 20.1 秒。如果系统是双核的,而且线程是标准线程,那么 A B 两个线程就可以真并行,总时间只需要 10 秒,而协程的方案仍然需要 20.1 秒。
(11)HashMap是线程安全的么,底层怎么实现的(get,set,resize),
JDK1.8之前和之后做了哪些修改,如果要使得插入kv有序需要使用哪种HashMap(LinkedHashMap,TreeMap),ConcurrentHashMap线程安全是怎么实现的(JDK1.8前后实现不同)
hashMap线程不安全的原因及表现
https://blog.csdn.net/VIP_WangSai/article/details/70182933
重新认识HashMap(in JDK1.8)
https://blog.csdn.net/SIMPLE1995/article/details/51822519
在JDK1.8版本中,对数据结构(数组+链表)做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树
HashMap底层实现原理及面试问题
https://blog.csdn.net/suifeng629/article/details/82179996
HashMap,LinkedHashMap,TreeMap的区别
https://blog.csdn.net/xin_jmail/article/details/25975085
TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。
ConcurrentHashMap是如何实现线程安全的(JDK1.8前后实现不同)
https://blog.csdn.net/qq_41737716/article/details/90549847
其中1.7的实现也同样采用了分段锁的技术,只不过多个一个segment,一个segment里对应一个小HashMap,其中segment继承了ReentrantLock,充当了锁的角色,一把锁锁一个小HashMap(相当于多个Node),从1.8的实现来看, 锁的粒度从多个Node级别又减小到一个Node级别,再度减小锁竞争,减小程序同步的部分。
(12)CountDownLatch、CyclicBarrier、Semaphore区别,使用场景
CountDownLatch、CyclicBarrier、Semaphore共同之处与区别以及各自使用场景
https://blog.csdn.net/jackyechina/article/details/52931453
(13)线程池分几种类型,其中的coreSize、maxSize、存活时间、等待队列、拒绝策略要清楚
Java线程池实现原理详解
https://blog.csdn.net/u013332124/article/details/79587436
深入理解线程和线程池(图文详解)
https://blog.csdn.net/weixin_40271838/article/details/79998327
corePoolSize: 规定线程池有几个线程(worker)在运行。
maximumPoolSize: 当workQueue满了,不能添加任务的时候,这个参数才会生效。规定线程池最多只能有多少个线程(worker)在执行。
keepAliveTime: 超出corePoolSize大小的那些线程的生存时间,这些线程如果长时间没有执行任务并且超过了keepAliveTime设定的时间,就会消亡。
unit: 生存时间对于的单位
workQueue: 存放任务的队列
threadFactory: 创建线程的工厂
handler: 当workQueue已经满了,并且线程池线程数已经达到maximumPoolSize,将执行拒绝策略。
(14)Forkjoin模型
Fork/Join是一个分而治之的任务框架,如一个任务需要多线程执行,分割成很多块计算的时候,可以采用这种方法。
java8学习第三篇:forkjoin
https://blog.csdn.net/java8cn/article/details/22819735
Fork/Join框架原理解析
https://blog.csdn.net/tyrroo/article/details/81483608
ForkJoin详解
https://blog.csdn.net/qq_28822933/article/details/83273817
(15)ArrayList和LinkedList的区别,栈和队列的区别。Queue和Deque区别
ArrayList和LinkedList的区别以及优缺点
对于ArrayList,它在集合的末尾删除或添加元素所用的时间是一致的,但是在列表中间的部分添加或删除时所用时间就会大大增加。但是它在根据索引查找元素的时候速度很快。
对于LinkedList则相反,它在插入、删除集合中任何位置的元素所花费的时间都是一样的,但是它根据索引查询一个元素的时候却比较慢。
ArrayList和LinkedList的大致区别:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。
2.对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
他们在性能上的有缺点:
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是 统一的,分配一个内部Entry对象。
2.在ArrayList集合中添加或者删除一个元素时,当前的列表所所有的元素都会被移动。而LinkedList集合中添加或者删除一个元素的开销是固定的。
3.LinkedList集合不支持 高效的随机随机访问(RandomAccess),因为可能产生二次项的行为。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
所以在我们进行对元素的增删查操作的时候,进行 查操作多时用ArrayList,进行增删操作多的时候最好用LinkedList。
栈和队列的区别
队列(Queue):是限定只能在表的一端进行插入和另一端删除操作的线性表
栈(Stack):是限定之能在表的一端进行插入和删除操作的线性表
队列和栈的规则
队列:先进先出
栈:先进后出
队列和栈的遍历数据速度
队列:基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,无需开辟空间,因为在遍历的过程中不影响数据结构,所以遍历速度要快
栈:只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来,遍历数据时需要微数据开辟临时空间,保持数据在遍历前的一致性
Queue和Deque区别
Queue是队列,Deque是双端队列。
1、Queue队列, 一种常用的数据结构,可以将队列看做是一种特殊的线性表,该结构遵循的先进先出原则。Java中,LinkedList实现了Queue接口,因为LinkedList进行插入、删除操作效率较高
相关方法:
boolean offer(E e):将元素追加到队列末尾,若添加成功则返回true。
E poll():从队首删除并返回该元素。
E peek():返回队首元素,但是不删除
2、Deque双向队列,指该队列两端的元素既能入队(offer)也能出队(poll),如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。对于栈而言,有入栈(push)和出栈(pop),遵循先进后出原则
常用方法如下:
void push(E e):将给定元素”压入”栈中。存入的元素会在栈首。即:栈的第一个元素
E pop():将栈首元素删除并返回。
方法上的区别如下:
Queue Deque
add addFirst
offer offerFirst
remove removeFirst
poll pollFirst
element getFirst
peek peekFirst
其中poll和add的区别就是add会抛出NullPointException异常,而offer会返回null。
Netty和Jetty
(16)Netty,Jetty实现原理。
Netty 原理
https://blog.csdn.net/truelove12358/article/details/106230654
- Netty基本概念
Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。
作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。如:Dubbo、 RocketMQ、Hadoop的Avro、Spark等。
Netty需要学习的内容: 编解码器、TCP粘包/拆包及Netty如何解决、ByteBuf、Channel和Unsafe、ChannelPipeline和ChannelHandler、EventLoop和EventLoopGroup、Future等。
Netty面试题
https://blog.csdn.net/PCCEO1/article/details/95899920
jetty架构及工作原理
https://blog.csdn.net/peterzhihua/article/details/86421360
剖析Jetty的运行原理
https://blog.csdn.net/zhaowen25/article/details/40899425
java基础
(17)Java回调
Java回调函数理解和应用
所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法。
(18)Java 静态代理、动态代理
java经典讲解-静态代理和动态代理的区别
https://blog.csdn.net/fangqun663775/article/details/78960545
(19)JDK1.8有什么新特性,了解函数式编程么(不了解的看看guava)
JDK1.8 新特性
https://blog.csdn.net/qq_29411737/article/details/80835658
Java函数式编程详解
https://blog.csdn.net/CDW2328/article/details/89852962