代码复习遇到知识点回顾(一)

Obsidian 插件篇 - 复习回顾 Obsidian_to_Anki 本篇文章由复习回顾的需求开始,引出 Spaced Repetition 插件、SuperMemo、Anki 的对比,最后详细分享 Ob 集成 Anki。 阅读详情

《1》静态代码块和构造代码块的区别

首先在执行顺序上:

静态代码块 > 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()触发] 父类构造代码块 - 父类构造方法 - 子类构造代码块 - 子类构造方法;


《2》Socket的获取与TCP的握手

对于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. SYN-SEND:当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即它会进入到了SYN_SENT状态;
  2. SYN_RECV:与SYN_SEND遥相呼应,当接收到SYN标识时,服务端会同时发送ACK标识和SYN标识服务端随机进入SYN_RECV状态;
  3. ESTABLISHED:当客户端接受并确认服务端发送来的ACK和SYN后,发送ACK标识,随机进入ESTABLISHED状态,当服务端接收到第三次握手的ACK状态后,随机也会进入到ESTABLISHED状态;
  4. FIN_WAIT1:当客户机发送断开标识FIN到服务端时,客户端进入FIN_WAIT1状态,这个状态表示在等待对方的FIN标识;
  5. CLOSE_WAIT:当收到客户端发送的FIN标识后,服务端进入到CLOSE_WAIT状态,接下来会检测自己是否还有数据发送给客户端,没有发送FIN标识进入LAST_AKC状态;
  6. FIN_WAIT2:当服务端发送ACK标识后,客户端确认后进入到FIN_WAIT2状态,此时SOCKET处于半链接状态,也就是不能给服务端写数据了,但可以继续接受服务端发送的数据;
  7. LAST_ACK:服务端发送FIN到客户端,自己进入LAST_ACK状态,等待客户端的ACK标识。
  8. TIME_WAIT:客户端发送ACK自己进入TIME_WAIT状态,等待2MSL后即可回到CLOSED状态;
  9. CLOSED:当服务端收到客户端发送的ACK后自己进入CLOSED状态;
  10. CLOSING;

上面的几个状态是从三次握手到四次挥手每个过程中可能出现的情况;结合下图中进行理解更加方便:

推荐两篇很不错的博文:

(1)Socket简单应用详解  

(2)TCP通信的三次握手和四次撒手的详细流程(顿悟)


《3》十六进制与字节的转换,以及位运算问题

十六进制(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;   
}  

位运算

  • 按位与运算(&): 两个都是 1 才是 1;
  • 按位或运算(|): 有 1 就得 1;
  • 按位异或运算(^): 不同就是 1 ;
  • 按位取反(~): 0 1 互换
  • 按位左移(<<): 右侧补0,在数字没有溢出的情况下相当于乘以2的移动位次方;
  • 按位右移(>>):左侧补0,在数字没有溢出的情况下相当于除以2的移动位次方;

《4》Thread   Runnalbe   Callable   ExecutorService   ThreadPoolExecutor

Thread 和 ExecutorService 区别:

Thread :

  • Thread 每次new 线程,线程新建和回收性能开销比较大;
  • Thread 缺乏对线程的控制,可以无限的新建线程;难易控制并发线程的数量,容易产生OOM;
  • Thread 的功能单一,如缺乏定时,周期执行等功能;

Executors 创建的线程池:

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  • 提供定时执行、定期执行、单线程、并发数控制等功能;

ThreadPoolExecutor :

Java多线程复习与巩固(六)--线程池ThreadPoolExecutor详解

ThreadPoolExecutor最佳实践--如何选择线程数

Executor详解

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

下面解释一下第四个构造器中各个参数的含义:

  •  corePoolSize:核心池的大小。

        核心池中的线程会一致保存在线程池中(即使线程空闲),除非调用allowCoreThreadTimeOut方法允许核心线程在空闲后一定时间内销毁,该时间由构造方法中的keepAliveTime和unit参数指定;
        在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartAllCoreThreads() 或者 prestartCoreThread()方法,从这两个方法的名字就可以看出,是“预创建线程”的意思,即在没有任务到来之前就创建corePoolSize个线程(prestartAllCoreThreads)或者一个线程(prestartCoreThread);

  • maximumPoolSize:线程池允许的最大线程数。它表示在线程池中最多能创建多少个线程。

        默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把新加入的任务放到缓存队列当中,缓存队列由构造方法中的workQueue参数指定,如果入队失败(队列已满)则尝试创建临时线程,但临时线程和核心线程的总数不能超过maximumPoolSize,当线程总数达到maximumPoolSize后会拒绝新任务;所以有两种方式可以让任务绝不被拒绝:

    ① 将maximumPoolSize设置为Integer.MAX_VALUE(线程数不可能达到这个值),CachedThreadPool就是这么做的;

    ② 使用无限容量的阻塞队列(比如LinkedBlockingQueue),所有处理不过来的任务全部排队去,FixedThreadPool就是这么做的。

  •  keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。

        默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用——当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会被销毁,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(true)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

  • workQueue:一个阻塞队列(BlockingQueue接口的实现类),用来存储等待执行的任务,一般来说,这里的阻塞队列有以下几种选择:
  1. ArrayBlockingQueue    数组实现的阻塞队列,数组不支持自动扩容。所以当阻塞队列已满,线程池会根据handler参数中指定的拒绝任务的策略决定如何处理后面加入的任务
  2. LinkedBlockingQueue  链表实现的阻塞队列,默认容量Integer.MAX_VALUE(不限容),当然也可以通过构造方法限制容量
  3. SynchronousQueue    零容量的同步阻塞队列,添加任务直到有线程接受该任务才返回,用于实现生产者与消费者的同步,所以被叫做同步队列
  4. PriorityBlockingQueue  二叉堆实现的优先级阻塞队列
  5. DelayQueue   延时阻塞队列,该队列中的元素需要实现Delayed接口;底层使用PriorityQueue的二叉堆对Delayed元素排序;ScheduledThreadPoolExecutor底层就用了DelayQueue的变体"DelayWorkQueue";队列中所有的任务都会封装成ScheduledFutureTask对象(该类已实现Delayed接口)      
  • threadFactory:线程工厂,主要用来创建线程;默认情况都会使用Executors工具类中定义的默认工厂类DefaultThreadFactory。可以实现ThreadFactory接口来自己控制创建线程池的过程(比如设置创建线程的名字、优先级或者是否为Deamon守护线程)
  • handler:表示当拒绝处理任务时的策略,有以下四种取值(默认为AbortPolicy):
  1. ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。
  2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

Executor 可 以 创 建 3 种 类 型 的 ThreadPoolExecutor 线 程 池:

 1. FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) { 
        return new ThreadPoolExecutor(nThreads, nThreads, 0L,
                                      TimeUnit.MILLISECONDS, 
                                      new LinkedBlockingQueue<Runnable>()); 
}  

1. FixedThreadPool的corePoolSize和maxiumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。
2. 0L则表示当线程池中的线程数量操作核心线程的数量时,多余的线程将被立即停止
3. LinkedBlockingQueue作为线程池的做工队列,由于是无界的,当线程池的线程数达到corePoolSize后,
   新任务将在无界队列中等待,因此线程池的线程数量不会超过corePoolSize,同时maxiumPoolSize也就
   变成了一个无效的参数,并且运行中的线程池并不会拒绝任务。

2. SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

1.如果当前工作中的线程数量少于corePool的数量,就创建一个新的线程来执行任务。

2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。

3.线程执行完1中的任务后会从队列中去任务。

注意:由于在线程池中只有一个工作线程,所以任务可以按照添加顺序执行。

3. CachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

1. CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为
   Integer.MAX_VALUE,即maximum是无界的。这里keepAliveTime设置为60秒,意味着空闲的线
   程最多可以等待任务60秒,否则将被回收
2. 如果主线程提交任务的速度高于线程池中处理任务的速度时,CachedThreadPool会不断创建新线程。
   极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU资源

4. ScheduleExecutorService

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
* <tt>initialDelay</tt> then <tt>initialDelay+period</tt>, then
* <tt>initialDelay + 2 * period</tt>, 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); 


《5》JAVA 类加载机制

参考博文:

(1)JVM系列(一):java类的加载机制

(2)Java 类加载机制(阿里面试题)-何时初始化类

类的生命周期:

Loading(加载)--- Verification(验证)--- Preparation(准备)--- Resolution(解析)--- Initialization(初始化);

加载过程:

第一个问题:加载class的方式有哪些?

  1.  从本地系统中直接加载
  2.  通过网络下载.class文件
  3.  从zip,jar等归档文件中加载.class文件
  4. 从专有数据库中提取.class文件
  5.  将Java源文件动态编译为.class文件

第二个问题:加载class文件中的二进制数据,虚拟机完成了那三件事情:

    1、通过一个类的全限定名来获取其定义的二进制字节流。

    2、将这个字节流中所代表的静态存储结构转化为方法区的运行时数据结构。

    3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

第三个问题:双亲委派机制是什么?

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器(子加载器和父加载器之间不是继承关系,而是组合关系)去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类:系统类防止内存中出现多份同样的字节码 ;保证Java程序安全稳定运行。

。

也就是说:

  • 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  • 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
  • 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  • 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

从启动类加载器 -- 扩展类加载器 -- 应用类加载器 -- 自定义类加载器,它们各自的搜索范围是什么?

  • 启动类加载器,Bootstrap ClassLoader,由C++编写,无法由java程序引用,加载JACA_HOME\jre\lib,或者被-Xbootclasspath参数限定的类
  • 扩展类加载器,Extension ClassLoader,由 该加载器由sun.misc.Launcher$ExtClassLoader实现,加载JAVA_HOME\jre\lib\ext,或者被java.ext.dirs系统变量指定路径下的所有类库;
  • 应用程序类加载器,Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,加载ClassPath中的类库
  • 自定义类加载器,通过继承ClassLoader实现,一般是加载我们的自定义类

JVM类加载机制

  • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

Class.forName()和ClassLoader.loadClass()区别

  • Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
  • ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

验证过程:

这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

  • 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
  • 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备阶段:

准备阶段是正式为 类变量 分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

  •     这时候进行内存分配的仅包括类变量(static),而不包括 实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
  •     这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

   假设一个类变量的定义为:public static int value = 3;

   那么变量value在准备阶段过后的初始值为 0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把value赋值为3的动作将在 初始化阶段 才会执行。

  • 如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。

   假设上面的类变量value被定义为: public static final int value = 3;

   编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中

解析阶段:

将常量池中所有的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)。这个阶段可以在初始化之后再执行。

初始化阶段:

初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

  1.   ①声明类变量是指定初始值
  2.   ②使用静态代码块为类变量指定初始值

 JVM初始化步骤

  •  假如这个类还没有被加载和连接,则程序先加载并连接该类
  •  假如该类的直接父类还没有被初始化,则先初始化其直接父类
  •  假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • – 创建类的实例,也就是new的方式
  • – 访问某个类或接口的静态变量,或者对该静态变量赋值
  • – 调用类的静态方法
  • – 反射(如Class.forName(“com.shengsiyuan.Test”))
  • – 初始化某个类的子类,则其父类也会被初始化
  • – Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
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:
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,
只会触发父类的初始化而不会触发子类的初始化。

《6》BlockingQueue 中 ArrayBlockingQueue 和 LinkedBlockingQueue 的区别

参考博文,一点说的比较好,一个锁,不同锁

存入数据:

  • offer(anObject): 如果BlockingQueue可以容纳,则返回true,否则返回 false.(本方法不阻塞当前执行方法的线程);       
  • offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
  • put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻塞.

获取数据:

  • poll() 或 poll(time): 取走BlockingQueue里排在首位的对象, 若不能立即取出, 则可以等time参数规定的时间,取不到时返回 null;
  • poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
  • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入; 
  • drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

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),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。


《7》CopyOnWriteArrayList 和 CopyOnWriteArraySet

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》单例模式

实现思路:

  • 私有化构造方法,让使用者无法通过 new 关键字创建;
  • 提供一个静态方法getInstance(),让用户通过类名直接获取这个唯一的对象;

常用场景:

  • 缓存,线程池,连接池,内嵌FTP,内嵌MQ 。。。。。。

常见写法,多线程模式下:

饿汉式:类在加载和初始化时,就创建,缺点:没满足懒加载, 可能浪费空间,优点 简单

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;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

k8s RoCE 部署: k8s-rdma-shared-dev-plugin + macvlan cni 写给自己的入门篇 文章目录前言一、创建 k8s 集群二、启用 primary network三、启用 secondary networkk8s-rdma-shared-dev-pluginMultus CNISecondary CNIMulti-Network CRD四、启用 pod五、在 pod 中启动 RoCE 流量总结 前言 写给自己的扫盲篇。后续会在原理方面持续更新 一、创建 k8s 集群 k8s 集群的创建有多种方法,可以按照官方文档的说明来. 阅读详情

相关推荐

AUTOSAR入门

一、AUTOSAR是什么 AUTOSAR组织自己的介绍是 AUTOSAR (AUTomotive Open System ARchitecture) is a worldwide development partnership of vehicle manufacturers, suppliers, service providers and companies from the automot...

天涯遍地是小草的博客 3万+

Java中的数据类型及运算符

Evernote Export 数据类型和运算符 一、变量和类型 变量:程序运行时可改变的量,开辟内存空间来保存数据 变量作用域:{ }中 注意:在Java中,局部变量使用前一定要先初始化,否则编译无法通过。 类型:对变量的种类进行划分 1.数据变量 数据单位转换: 1Bytes=8 bits(1字节等于8位) 1KB=1024 Bytes(1KB等...

qq_45605759的博客 808

基于分数阶傅里叶变换的chirp信号检测与参数估计(原理附代码)

线性调频信号(chirp信号) 顾名思义,该信号的频率随着时间线性变换,其复数表达形式如下: s(t)=e2jπ(f0t+0.5μt2)s(t)=e^{2j\pi(f_0 t+ 0.5\mu t^2)}s(t)=e2jπ(f0​t+0.5μt2) 根据欧拉公式,其相位项为2π(f0t+0.5μt2))2\pi(f_0 t+ 0.5\mu t^2))2π(f0​t+0.5μt2))。信号的角频率是相位对时间的导数。对相位求导后有2π(f0+μt)2\pi(f_0+\mu t)2π(f0​+μt),显然,f0f

weixin_42845306的博客 1万+

>> << 0xf 等用法

一、>> 这是C语言中的“右移运算符”。一般情况下,他是按位操作。 特点:1.双目运算符,就像+、-、*、/一样,格式为(数1)>>(数2) 2.数1是被操作数,如被减数、被除数等;数2是右移位数。 3.优先级低,结合性:从左向右运算 如:8>>1=?表示将被移数向右移动1位 如何操作? 1.化十进制数为(注意了)对应的二进制数,对应指格式对应 2.通通右移,正数左补

wangdamingll的博客 1万+

MTK眼中6G物理层与无线协议的进化之路

提议统一波束和小区级的测量、报告和切换框架/流程,进一步减少移动性切换过程中的中断,采用可扩展框架/流程,允许在不同移动性场景中进行部分用户设备配置更新以实现快速切换,支持用户设备发起的移动性(同步/切换)以降低延迟和因信令交换导致的失败,设计移动性RS以实现空闲状态下的节能和连接状态下的无缝用户体验,实现5G/6G间的无缝互操作移动性,为固定无线接入(FWA)提供潜在增强,构建统一的跨层移动性框架以降低复杂度、减少中断和失败。为适应多样化的流量类型,需要灵活的帧结构和高层时间对齐。

asd199086的博客 1745

IMU卡尔曼滤波方法详细介绍

本文介绍了基于IMU和卡尔曼滤波的状态估计方法。系统采用9维状态向量(位置、速度、加速度计零偏),通过牛顿运动学建立状态方程,并利用外部位置观测进行校正。详细推导了状态转移矩阵和控制输入矩阵的形式,阐述了预测和更新的计算步骤。同时说明了过程噪声Q和观测噪声R的设置原则,其中Q反映模型不确定性,R取决于传感器精度。该方法可有效融合IMU测量与外部观测数据,实现位置、速度和偏置的联合估计。

haing2019的博客 754

计算机组成原理——第二章数据的级表示

计算机组成原理——第二章 数据的级表示 本章重点讨论计算机内部数据的机器级表示方式。主要内容包括:进位记数制、二进制定点数的编码表示、无符号整数和带符号整数的表示、IEEE 754 浮点数表示标准、西文字符和汉字的编码表示、十进制数的二进制编码表示(即 BCD 码)、C 语言中各种类型数据的表示和转换、数据的宽度和存放顺序以及几种常用检/纠错码的编码表示与使用方法。 2.1 数制和编码 2.1.1...

wxy2635618879的博客 1275

用AnythingLLM搭建私人RAG知识库:从入门到实战调优指南

大型语言模型虽然知识渊博,但无法触达私有文档中的具体内容,这正是检索增强生成(RAG)技术的核心价值所在。RAG通过先将文档切块并向量化,再在提问时检索最相关的片段拼接给模型,让AI基于指定资料精准回答,无需昂贵的模型微调。这种架构不仅让知识更新即时生效,还大幅降低了私有化部署的门槛。在实际工程中,RAG的落地需要合理选择嵌入模型、设计分块策略并调优检索参数,才能避免答非所问。对于个人或小团队,使用开源工具搭建本地知识库是最省心的路径,其中AnythingLLM以其开箱即用的特性,成为连接向量检索与模型推理

weixin_29062671的博客 227

Byte为什么要做0xFF与运算

最近看代码, 其中看到MD5加密的时候感到很有意思, 就是为什么得到了byte数组后, 每个byte转成十六进制之前, 转换成整数的时候, 都要和 0xFF 做一遍与运算. 小弟看了很是很是困惑 demo代码 public static String clacMD5(String str){ try{ MessageDigest md = MessageDigest.getInstance(...

weixin_38137028的博客 532

无线网卡选购指南:哪些型号支持Kali Linux的监听模式?附免驱动网卡推荐

本文详细解析了如何在Kali Linux下选购支持监听模式的无线网卡。核心在于选择兼容的芯片组,如Atheros AR9271和Ralink RT3070等经典型号,并提供了免驱动网卡推荐与实战配置指南,帮助安全研究人员和爱好者快速搭建无线测试环境。

wind6的博客 1552

【WLAN】【调试】如何使用Wireshark分析加密的WiFi数据包

- 当配置网卡为monitor 模式的情况下,可以使用 wireshark来抓取空口报文; - 如果WiFi采用非Open加密方式,则从报文中仅能分析到 802.11协议层,更底层(如DHCP、ICMP、TCP等)被加密,此时可以使用 wireshark 来完成解密。 - 解密之后,可以通过icmp、tcp等关键词过滤报文。

一介草民,只为生活。 2984

UnicodeDecodeError: 'ascii' codec can't decode byte 0xf6 in position 0: ordinal not in range(128)

1. 问题:UnicodeDecodeError: 'ascii' codec can't decode byte 0xf6 in position 0: ordinal not in range(128) 出错误的地方在 第101代码 return pickle.load(f)处,如下图所示: # read aus conds_filepath = os.path.join...

weixin_39450145的博客 974

LeRobot 入门教程(二)强化学习训练及 SmolVLA 模型微调

本文介绍了LeRobot框架中基于人类在环的样本高效强化学习(HIL-SERL)工作流,包含真实机器人训练和模拟环境训练两大模块。在真实机器人部分,详细说明了从配置设置、工作空间边界确定、演示数据收集、奖励分类器训练到演员-学习者架构部署的全流程,并强调了人类干预的关键作用。同时介绍了适用于模拟训练的gym_hil环境及其配置使用方法。最后,文章提供了微调SmolVLA基础模型的指南,包括数据集收集、微调步骤和模型评估方法。该工作流实现了人机协作的高效强化学习,适用于各类机器人操作任务。

2506_90492529的博客 3896

舒江林学期总结

安卓学期总结 一学期的安卓学习已经接近尾声,这一学期以来,我们已经从最初的软件安装,再到最后可以做出一个个小的安卓程序,这些都是我们一学期的成果。 一学期以来,我们从最基础的代码开始接触到安卓的基本知识,虽然还没有彻底地掌握安卓的所有知识,但是,经过一学期的学习,我们已经切身地体会到了安卓编程的魔力以及神奇之处,一个个简单的代码,用变幻多样的组成方式,构成一个个复杂且神奇的语句,安卓用它神奇的力量像我们展示了一个数字化的多彩世界。 安卓的学习应该是一个艰苦的漫长过程,就好像编程一样,我们需要做好底层架构,学

weixin_46303779的博客 2006

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

【期末复习】c++知识点大回顾,八篇文章让你永不破防(一)

一、第15章 介绍对象技术 1.简单输入输出 //求a和b两个数之和。 // 求两数之和 (本行是注释行) #include <iostream> //预处理命令 using namespace std; //使用命名空间std int main( ) //主函数首部 { //函数体开始 ......

行码棋 6714

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

【期末复习】c++知识点大回顾,八篇文章让你永不破防(二)

c++期末复习知识点最全整理 文章目录二、类与对象简介1.类与对象的描述2.类的特性3.类的定义及成员属性4.对象成员的访问5.构造函数和析构函数6.接口与实现分离7.条件编译7.1. 形式17.2.形式27.3. 形式37.4.应用:预处理器封套8.explicit关键字8.1.介绍8.2. 作用 二、类与对象简介 1.类与对象的描述 类:具有相同属性和方法的一组对象的集合。简而言之就是一个类别,比如说牛奶、花生、酒等等就是一个是事物的类别。 注: 类是对象的类型,可以把类与int等变量类型作比较,但

行码棋 4703

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

html+css知识点复习回顾,复习参考

这里写自定义目录标题一、HTML部分1、两个盒子2、图像标签3、相对路径和绝对路径4、超链接标签5、表格标签6、列表标签7、表单标签8、HTML5新特性二、CSS部分1、CSS选择器2、CSS字体属性3、CSS文本属性4、CSS引入方式5、CSS的元素显示模式6、CSS的背景样式7、CSS三大特性8、盒子模型9、传统网页布局的三种方式10、CSS属性书写顺序(规范代码)11、元素的显示与隐藏12、CSS高级技巧13、CSS3新特性 一、HTML部分 1、两个盒子 <div>、<span&g

weixin_55935744的博客 1470

文件烧录的时候,可以回顾下以前的知识点,算是复习下

分心了就相当于大脑切换了线程,切换回来需要消耗精力和时间。文件烧录的时候,可以回顾下以前的知识点,算是复习下。

he30837993的博客 109

【免费下载】 考研数学一知识点全攻略 (8K打印版)

考研数学一知识点全攻略 (8K打印版) 【下载地址】考研数学一知识点全攻略8K打印版分享 本仓库提供了一份珍贵的考研数学资料——《考研数学一全部知识点总结(8K打印).pdf》。这份文档专为准备参加研究生入学考试(统考数学一)的同学设计,旨在帮助大家高效复习,把握核心知识点。考研数学一覆盖高等数学、线性代数及概率论与数理...

gitblog_06650的博客 4327

Java 知识点基础回顾

本文回顾了Java基础知识点,主要包括Java版本分类(JavaEE、JavaSE、JavaME)、JDK/JRE/JVM的关系及安装配置、变量命名规则与数据类型(基本类型和引用类型)、数据类型转换规则(自动转换和强制转换)、以及运算符使用(算术、赋值、关系、逻辑等)。文章强调了Java跨平台特性、字符集编码原理、变量作用域和命名规范,并通过代码示例说明了类型转换中的常见问题和运算时的自动类型提升现象。全文系统梳理了Java基础概念,为后续深入学习打下基础。

Eric_smxu的博客 904

【期末复习】c++知识点大回顾,八篇文章让你永不破防(三)(建议收藏)

目录 三、再谈构造函数和析构函数 三、再谈构造函数和析构函数 1.

行码棋 3669

【高等数学】上册知识点复习

【高等数学】上册知识点复习

学无止境 3451

【期末复习】c++知识点大回顾,八篇文章让你永不破防(一)_计算机专业c

/求a和b两个数之和。//求两数之和(本行是注释行)#include<iostream>//预处理命令//使用命名空间stdintmain()//主函数首部{//函数体开始//定义变量cin>>a>>b;//输入语句//赋值语句//输出语句return0;//如程序正常结束,向操作系统返回一个零值}//函数结束cin和cout都在命名空间std中,如果要直接使用,需要声明命名空间。

2501_90252289的博客 968

python知识点总结回顾

本文总结了Python编程的基础知识,包括变量类型、循环语句、字符串操作、函数定义、文件处理等核心内容。文章详细介绍了Python的基本语法规则、常用数据类型(如列表、字典、元组)、控制流程(if判断、while/for循环)、字符串切片方法、函数参数传递机制以及文件读写操作。同时涵盖了异常处理、匿名函数等进阶知识点,并提供了格式化输出、随机数生成等实用技巧。通过系统梳理Python编程要点,旨在帮助读者快速回顾Python语言特性,适合初学者巩固基础或作为速查参考。

qq_57891619的博客 676

复习这么长时间,没有效果怎么办?

相信在学习过程中你一定也遇到了“学了背了又忘了,做题时都不会了”的尴尬问题。不要慌,其实这是正常现象,放好心态,一轮看完,其实大家都会有个共同的特点:容易忘,但是也还有个特点,很容易回忆...

计算机与软件考研 349

回顾软件设计师之复习之路

首先放上软考历年真题和相关视频 百度云盘:链接:https://pan.baidu.com/s/1Tmh3uQX3mMMjZEfLHvpD0A 密码:60t3 gitHub上有我对错题和知识点的一个梳理,有兴趣的可以参考下,还是希望只是参考参考,自己动手去整理这样复习会事半功倍 gitHub:https://github.com/cc7gs/Software-notes 下面谈下关于这次考...

前端小愤青 4096
上一篇: Redis 持久化
Coder-Qian
博客等级 码龄11年 63粉丝 73原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值