首先在执行顺序上:
静态代码块 > main 方法 > 构造代码块 > 构造方法
注意:在 JVM 加载类时,就会执行其静态代码块,所以静态代码块一定优于静态方法;
执行次数上:
静态代码块只执行一次,构造代码块每创建一个对象,就会执行一次;
在继承关系上,执行顺序:引文:静态代码块 构造代码块 构造方法的执行顺序及注意问题
class Fu {
// 静态代码块
static {
System.out.println("Fu static code");
}
// 构造代码块
{
System.out.println("Fu code");
}
// 构造方法
public Fu(){
System.out.println("Fu GouZao");
}
}
class Zi extends Fu{
static {
System.out.println("Zi static code");
}
{
System.out.println("Zi code");
}
public Zi(){
// 隐写了 super();
System.out.println("Zi GouZao");
}
}
public class Text{
public static void main(String[] args) {
Zi zi = new Zi();
}
}
// 输出结果
Fu static code
Zi static code
Fu code
Fu GouZao
Zi code
Zi GouZao
解释:
首先:在编译Text.java时,JVM 先加载了Fu类, 执行静态代码块内容,因此Fu类的静态代码块首先执行,而后加载Zi类,Zi类的静态代码块执行;
其次:然后执行 new Zi() 创建Zi的对象,大家都知道构造代码块优先于构造方法执行,这时候问题来了, 这时应该先看Zi类的构造方法,Zi类里的构造方法里有一句隐式的super()首先被执行, 所以找到Fu类的构造方法, 而Fu类的构造方法中也有一句隐式的super()执行(调用Object类的构造方法), 并没有什么返回结果, 接下来才是在执行Fu类构造方法的方法体前先执行了Fu类的构造代码块(Fu code) ,再执行Fu类构造方法的方法体(也就是Fu GouZao),最后又回到Zi类的构造方法中,这时Zi类的super()已经执行完了,在执行Zi类构造方法的方法体前先执行Zi类的构造代码块(Zi code),再执行Zi类构造方法的方法体(Zi GouZao)。
所以:[JVM触发]{父类静态代码块 - 子类静态代码块 } - [super()触发] 父类构造代码块 - 父类构造方法 - 子类构造代码块 - 子类构造方法;
对于TCP/IP网络的7层还是 5 层,我们这里暂定讨论 5 层的;
应用层 -- 传输层(TCP层解决端(PC)与端之间的通信,段)-- 网络层(IP层解决点(PC)与点(交换机)数据的传输,数据包)-- 链路层(解决物理传输中,帧)
首先回顾下TCP的三次握手 和 四次挥手
对于TCP的标识位,也就是位码,主要有:
(1)SYN(synchronous建立连接)(2)ACK(acknowledgement确认连接)(3)PSH(push传送)(4)FIN(finish结束)(5)RST(rest重置)(6)URG(urgent紧急)以及两个数字标识:顺序码(sequence number)和确认码(acknowledge number)
十种状态:
上面的几个状态是从三次握手到四次挥手每个过程中可能出现的情况;结合下图中进行理解更加方便:
推荐两篇很不错的博文:
(1)Socket简单应用详解
(2)TCP通信的三次握手和四次撒手的详细流程(顿悟)
十六进制(Hex):计算机中数据的一种表示方法,它由0-9,A-F(a-f)组成,字母不区分大小写。与10进制的对应关系是:0-9对应0-9;A-F对应10-15。4个二级制(0000~1111)有16种组合刚好对应如下16进制的字符;也就是4个二进制表示一个十六进制字符,一个字节等于8个二进制,等于2个16进制字符;【1byte = 8bit = 2 个16进制code】
0:0000 1:0001 2:0010 3:0011 4:0100 5:0101 6:0110 7:0111 8:1000 9:1001
A:1010 B:1011 C:1100 D:1101 E:1110 F:1111
那么:十六进制 0xF6对应的二进制应为:F:1111 ;6 : 0110 所以对应的二进制为 1111 0110 相应的十进制为:246
byte b = (byte) 5 ;对应的二进制0000 0101对应的16进制为 0x05;
/**
* 字节转十六进制
*/
public static String byteToHex(byte b){
String hex = Integer.toHexString(b & 0xFF);
if(hex.length() < 2){
hex = "0" + hex;
}
return hex;
}
output:
0x05 = byteToHex((byte)5);
0xF6 = byteToHex((byte)246);
public static String bytesToHex(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if(hex.length() < 2){
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
byte[] bArr = {5,(byte) 246,(byte) 213};
System.out.println(bytesToHex(bArr));
output:
05f6d5
/**
* Hex字符串转byte
* @param inHex 待转换的Hex字符串
* @return 转换后的byte
*/
public static byte hexToByte(String inHex){
return (byte)Integer.parseInt(inHex,16);
}
System.out.println(hexToByte("D"));
output:
13
/**
* hex字符串转byte数组
* @param inHex 待转换的Hex字符串
* @return 转换后的byte数组结果
*/
public static byte[] hexToByteArray(String inHex){
int hexlen = inHex.length();
byte[] result;
if (hexlen % 2 == 1){
//奇数
hexlen++;
result = new byte[(hexlen/2)];
inHex="0"+inHex;
}else {
//偶数
result = new byte[(hexlen/2)];
}
int j=0;
for (int i = 0; i < hexlen; i+=2){
result[j]=hexToByte(inHex.substring(i,i+2));
j++;
}
return result;
}
位运算
Thread 和 ExecutorService 区别:
Thread :
Executors 创建的线程池:
ThreadPoolExecutor :
Java多线程复习与巩固(六)--线程池ThreadPoolExecutor详解
ThreadPoolExecutor最佳实践--如何选择线程数
Executor详解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
下面解释一下第四个构造器中各个参数的含义:
核心池中的线程会一致保存在线程池中(即使线程空闲),除非调用allowCoreThreadTimeOut方法允许核心线程在空闲后一定时间内销毁,该时间由构造方法中的keepAliveTime和unit参数指定;
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartAllCoreThreads() 或者 prestartCoreThread()方法,从这两个方法的名字就可以看出,是“预创建线程”的意思,即在没有任务到来之前就创建corePoolSize个线程(prestartAllCoreThreads)或者一个线程(prestartCoreThread);
默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把新加入的任务放到缓存队列当中,缓存队列由构造方法中的workQueue参数指定,如果入队失败(队列已满)则尝试创建临时线程,但临时线程和核心线程的总数不能超过maximumPoolSize,当线程总数达到maximumPoolSize后会拒绝新任务;所以有两种方式可以让任务绝不被拒绝:
① 将maximumPoolSize设置为Integer.MAX_VALUE(线程数不可能达到这个值),CachedThreadPool就是这么做的;
② 使用无限容量的阻塞队列(比如LinkedBlockingQueue),所有处理不过来的任务全部排队去,FixedThreadPool就是这么做的。
默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用——当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会被销毁,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(true)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
Executor 可 以 创 建 3 种 类 型 的 ThreadPoolExecutor 线 程 池:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
1. FixedThreadPool的corePoolSize和maxiumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。
2. 0L则表示当线程池中的线程数量操作核心线程的数量时,多余的线程将被立即停止
3. LinkedBlockingQueue作为线程池的做工队列,由于是无界的,当线程池的线程数达到corePoolSize后,
新任务将在无界队列中等待,因此线程池的线程数量不会超过corePoolSize,同时maxiumPoolSize也就
变成了一个无效的参数,并且运行中的线程池并不会拒绝任务。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
1.如果当前工作中的线程数量少于corePool的数量,就创建一个新的线程来执行任务。
2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。
3.线程执行完1中的任务后会从队列中去任务。
注意:由于在线程池中只有一个工作线程,所以任务可以按照添加顺序执行。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
1. CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为
Integer.MAX_VALUE,即maximum是无界的。这里keepAliveTime设置为60秒,意味着空闲的线
程最多可以等待任务60秒,否则将被回收
2. 如果主线程提交任务的速度高于线程池中处理任务的速度时,CachedThreadPool会不断创建新线程。
极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU资源
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
1. scheduleAtFixedRate方法,当我们要执行的任务大于我们指定的执行间隔时,
它并不会在指定间隔时开辟一个新的线程并发执行这个任务。而是等待该线程执行完毕。
2. 当任何一个任务的执行过程中遇到一个异常,剩下的执行将会被抑制,
* Creates and executes a periodic action that becomes enabled first
* after the given initial delay, and subsequently with the given
* period; that is executions will commence after
* initialDelay then initialDelay+period, then
* initialDelay + 2 * period, and so on.
* If any execution of the task
* encounters an exception, subsequent executions are suppressed.
* Otherwise, the task will only terminate via cancellation or
* termination of the executor. If any execution of this task
* takes longer than its period, then subsequent executions
* may start late, but will not concurrently execute.
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
参考博文:
(1)JVM系列(一):java类的加载机制
(2)Java 类加载机制(阿里面试题)-何时初始化类
类的生命周期:
Loading(加载)--- Verification(验证)--- Preparation(准备)--- Resolution(解析)--- Initialization(初始化);
加载过程:
第一个问题:加载class的方式有哪些?
第二个问题:加载class文件中的二进制数据,虚拟机完成了那三件事情:
1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流中所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
第三个问题:双亲委派机制是什么?
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器(子加载器和父加载器之间不是继承关系,而是组合关系)去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类:系统类防止内存中出现多份同样的字节码 ;保证Java程序安全稳定运行。
。
也就是说:
从启动类加载器 -- 扩展类加载器 -- 应用类加载器 -- 自定义类加载器,它们各自的搜索范围是什么?
JVM类加载机制
Class.forName()和ClassLoader.loadClass()区别
验证过程:
这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备阶段:
准备阶段是正式为 类变量 分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
假设一个类变量的定义为:public static int value = 3;
那么变量value在准备阶段过后的初始值为 0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器
假设上面的类变量value被定义为: public static final int value = 3;
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中
解析阶段:
将常量池中所有的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)。这个阶段可以在初始化之后再执行。
初始化阶段:
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
JVM初始化步骤
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
public class SSSClass {
static {
System.out.println("SSSClass");
}
}
public class SSClass extends SSSClass {
static{
System.out.println("SSClass init!");
}
public static int value = 123;
public SSClass(){
System.out.println("init SSClass");
}
}
public class SClass extends SSClass{
static {
System.out.println("SClass init");
}
static int a;
public SClass() {
System.out.println("init SClass");
}
}
public class NotInitialization
{
public static void main(String[] args) {
System.out.println(SClass.value);
}
}
output:
SSSClass
SSClass init!
123
REASON:
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,
只会触发父类的初始化而不会触发子类的初始化。
参考博文,一点说的比较好,一个锁,不同锁
存入数据:
获取数据:
1. ArrayBlockingQueue
基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
2.LinkedBlockingQueue
基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
CopyOnWrite容器即 写时复制 的容器。通俗的理解是:当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。但是当我们涉及到更改容器值的操作时(add set remove),都是在lock中进行的,这也保证了多线程写的一致性;所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器;由于更改容器的元素是copy出来的一份,所以在内存中,在某一段时间就会存在两份容器数据,导致非常占内存,其次,我们在修改完以后又把新的容器(copy出来的这份)的引用赋给原来容器的引用,这就导致了刚修改的元素可能不能立即读取到,于此同时频繁的更赋引用也容易导致GC回收问题;因此,CopyOnWrite容器适合于高频度读取操作,低频度修改的操作;
内部源码:
获取操作 get(index)
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
final Object[] getArray() {
return array;
}
添加操作:add(E e) remove( int index);
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 注意 在此先进行lock,也就序列化了多线程增加问题;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 注意, 复制了一份出来, 并且新的长度增加了一个;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 将原来数组的引用指向了新的引用;
setArray(newElements);
return true;
} finally {
// finally 中释放锁
lock.unlock();
}
}
final void setArray(Object[] a) {
array = a;
}
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
注意上面的:
Arrays.copyOf(elements, len + 1);
System.arraycopy(elements, 0, newElements, 0, index);
《8》单例模式
实现思路:
常用场景:
常见写法,多线程模式下:
饿汉式:类在加载和初始化时,就创建,缺点:没满足懒加载, 可能浪费空间,优点 简单
public class Singleton {
private static fianl Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance (){
return INSTANCE;
}
}
public class Singleton {
private static Singleton INSTANCE;
static {
INSTANCE = new Singleton();
}
private Singleton(){}
public static Singleton getInstance (){
return INSTANCE;
}
}
懒汉式:优点延迟加载,缺点需要 sychronized 加 双重判断,存在锁竞争问题;
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static sychronized Singleton getInstance(){
if( null == instance ){
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if( null == instance ){
synchronized (Singleton.class) {
if( null == instance )
instance = new Singleton();
}
}
return instance;
}
}
私有静态内部类: 也可以使用到锁,并且不用使用同步代码块,也可以
public class Singleton {
private Singleton(){}
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
枚举:简单方便,缺点 不好理解
public enum Singleton{
INSTANCE;
}