整理

238 阅读19分钟

一、聊聊volatile关键字

  • volatile关键字是java提供的一种轻量级的同步机制,java语言包含两种内在的同步机制:同步块(同步方法)和volatile变量,synchronized通常称之为重量级锁,相比于synchronized,volatile更轻量级,因为它不会引起线程上下文的切换和调度。
  • 保证可见性,不保证原子性
    • java内存模型规定了所有的变量都存储在主内存当中,此外每个线程都有自己的工作内存,线程的工作内存中保存着该线程使用到的所有变量的主内存副本。
    • 当一个变量被volatile修饰后,JMM(java内存模型)规定该线程对该变量执行完操作以后,会立即将该线程本地内存中的变量值强制刷新到主内存中去。并且会使其他线程中的缓存失效。
  • 禁止指令重排:观察底层生成的汇编代码时,加入volatile关键字的变量,会在其加上lock前缀指令,lock前缀指令实际相当于一个内存屏障,它确保指令重排序时不会把其后的指令排序到内存屏障之前的位置,也不会把前面的指令排序到内存屏障的后面。

二、单例模式的双重锁(DCL)为什么要加volatile

需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。instance = new TestInstance();可以分解为3行伪代码

a.memory = allocate() //分配内存

b. ctorInstanc(memory) //初始化对象

c. instance = memory //设置instance指向刚分配的地址

上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。

三、java变量的初始化

  • 成员变量初始化

    • 整数类型(byte、short、int、long)的基本类型变量的默认值为0;
    • 单精度浮点型(float)的基本类型变量的默认值为0.0f;
    • 双精度浮点型(double)的基本类型变量的默认值为0.0d;
    • 字符型(char)的基本类型变量的默认为 “/u0000”;
    • 布尔型的基本类型变量的默认值为 false;
    • 引用类型的变量是默认值为 null;
    • 数组引用类型的变量的默认值为 null。当数组变量的实例后,如果没有显示的为每个元素赋值,Java 就会把该数组的所有元素初始化为其相应类型的默认值,如int[] a;初始化a为null;若为int[] a = new int[5];则in[0]-in[4]都初始化为0。
  • 局部变量初始化

    • 局部变量声明以后,Java 虚拟机不会自动的为它初始化为默认值,因此对于局部变量,必须先经过显示的初始化,才能使用它,如果编译器确认一个局部变量在使用之前可能没有被初始化,编译器将报错。

四、java关键字之native介绍

  • 1. JNI:Java Native Interface

    在介绍 native 之前,我们先了解什么是 JNI。

  一般情况下,我们完全可以使用 Java 语言编写程序,但某些情况下,Java 可能会不满足应用程序的需求,或者是不能更好的满足需求,比如:

  ①、标准的 Java 类库不支持应用程序平台所需的平台相关功能。

  ②、我们已经用另一种语言编写了一个类库,如何用Java代码调用?

  ③、某些运行次数特别多的方法代码,为了加快性能,我们需要用更接近硬件的语言(比如汇编)编写。

  上面这三种需求,其实说到底就是如何用 Java 代码调用不同语言编写的代码。那么 JNI 应运而生了。

  从Java 1.1开始,Java Native Interface (JNI)标准就成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计 的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。

通过 JNI,我们就可以通过 Java 程序(代码)调用到操作系统相关的技术实现的库函数,从而与其他技术和系统交互,使用其他技术实现的系统的功能;同时其他技术和系统也可以通过 JNI 提供的相应原生接口开调用 Java 应用系统内部实现的功能。

  在windows系统上,一般可执行的应用程序都是基于 native 的PE结构,windows上的 JVM 也是基于native结构实现的。Java应用体系都是构建于 JVM 之上。   可能有人会问,Java不是跨平台的吗?如果用 JNI,那么程序不就将失去跨平台的优点?确实是这样的。

  JNI 的缺点:

  ①、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。

  ②、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了JAVA和C之间的耦合性。

  目前来讲使用 JNI 的缺点相对于优点还是可以接受的,可能后面随着 Java 的技术发展,我们不在需要 JNI,但是目前 JDK 还是一直提供对 JNI 标准的支持。

  • 2.JNI调用C的流程图

  • 3.native关键字总结

native 用来修饰方法,用 native 声明的方法表示告知 JVM 调用,该方法在外部定义,我们可以用任何语言去实现它。 简单地讲,一个native Method就是一个 Java 调用非 Java 代码的接口。

  native 语法:

  ①、修饰方法的位置必须在返回类型之前,和其余的方法控制符前后关系不受限制。

  ②、不能用 abstract 修饰,也没有方法体,也没有左右大括号。

  ③、返回值可以是任意类型

  我们在日常编程中看到native修饰的方法,只需要知道这个方法的作用是什么,至于别的就不用管了,操作系统会给我们实现。

五、JAVA CAS原理深度分析

详情参考链接:[CAS原理深度分析一](blog.csdn.net/Hsuxu/artic…)
详情参考链接:[CAS原理深度分析二]blog.csdn.net/tiandao321/…
java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包。可见CAS的重要性。

  • CAS

    • CAS:Compare and Swap, 翻译成比较并交换。
    • java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。
  • 什么是CAS?

    • CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则,不要更改该位置,只告诉我这个位置现在的值即可。整个比较并替换的操作是一个原子操作。
  • CAS的缺点

    • 循环时间长开销很大:
      • 我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

    • 只能保证一个共享变量的原子操作:
      • 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

    • ABA问题:如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?
      • 如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。

六、sleep()和wait()的区别

参考博客链接:[sleep()和wait()的区别](blog.csdn.net/xyh269/arti…)

  • sleep()方法是线程类(Thead)的静态方法,使用该方法的调用线程进入睡眠状态,让出其持有的CPU执行权,等到休眠时间结束后,进入就绪状态,和其他线程一起竞争CPU的执行权。
  • 在synchronized同步代码块中调用sleep()方法以后,线程进入睡眠状态,让出CPU执行权,但不会释放该线程持有的对象锁,其他线程依然无法获取到该对象锁。
  • wait()是Object类的方法,当一个线程执行到wait方法时,该线程会被放入与该对象有关的锁池中,让出CPU执行权,同时释放其持有的对象锁,让其他线程可以访问,并等待其他线程通过notify,notifyAll方法来唤醒等待。或者使用带参数的wait()方法,在规定的超时等待时间之后,自行回到就绪态等待CPU的调度执行。

七、ReentrantLock和synchronized的区别

  • 1.ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。
  • 2.ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
  • 3.比起synchronized功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等等。可以配合一个或多个Condition条件方便的实现等待通知机制。

八、java:内部类与外部类的区别和联系

  • 1. 在内部类中可以随意使用外部类的成员方法以及成员变量:

    • 众所周知,在定义成员方法或者成员变量的时候,可以给其加上一些权限的修饰词,以防止其他类的访问。如在成员变量或者成员方法前面,加上Private 关键字,则其他类就无法调用这个类中的成员方法或则和成员变量。但是,如果这个类有成员内部类,则不受这方面的限制。也就是说,在成员内部类中可以随意引 用外部类的成员方法以及成员变量,即使这些类成员方法或者成员变量被修饰了private。如在成员外部类中定义了一个i变量,并且利用private关 键字来修饰。此时在其他类中是不能够引用这个私有成员变量的。但是这个类的成员内部类的方法中,则不仅可以引用这个外部类的私有变量,而且还可以对其进行 赋值等操作。这个赋值操作对于外部类也是有效的。即成员内部类可以更改外部类中私有变量的值。

  • 2. 要在外部类中实例化内部类对象的引用:

    • 如果一个类定义在另外一个类中,成为成员内部类,此时一定要注意,内部类的实例一定要绑定在外部类的实例上。也就是说,要从外部类中初始化一个内部类的对象,此时内部类的对象就会绑定在外部类的对象上。这跟普通的类有所不同。普通的类,创建完之后,不一定马上需要实例化。在需要用到这个对象的时候,再进行实例化即可。但是,如果一个类成为另外一个类的成员内部类,则就不同了。必须要在外部类中实例化内部类对象的引用,以实现将内部类的实例绑定在外部类的实例上。简单的说,就是在定义外部类的时候,如果有成员内部类,那么就不要望了在外部类中利用new关键字来实例化内部类对象的引用。而对于外部类来说, 则可以在需要的时候再进行实例化。如此就可以保证,利用外部类创建对象的同时创建了内部类的对象。从而可以保证内部类的实例绑定在外部类的实例上。

  • 3. 成员内部类中成员方法与成员变量的私有性:

    • 作为成员内部类,可以随意引用外部类中的成员变量与成员方法。那么在成员内部类中定义的成员变量,外部类是否也可以随意访问呢?答案是否定的。内部类可以访问它外部类的成员,但是内部类的成员(如成员变量或者成员方法)只有在内部类的范围之内是可知的,不能够被外部类直接引用。如现在在外部类中定义了一个变量i,在内部类中定义了另一个变量ii。此时在成员内部类中,可以直接引用这个外部类中的变量i,也可以对其直接进行赋值,如i=5等等。但是在外部类中,则不能够直接引用内部类中的成员变量。如在外部类中,利用ii=5的赋值语句改变这个变量的值,就属于语法错误,在编译的时候就会出现错误。如果外 部类真的要引用内部类的成员,那也不是不可以。只是不能够进行直接的引用,而是要使用内部类对象引用的方法才能够调用内部类的成员变量。这一点程序开发人员需要切记。成员内部类与外部类相互访问彼此的成员方法限制是不同的。特别需要注意的是,如果在外部类和非静态方法之外实例化内部对象,则需要使用外部类.内部类的形式来制定这个对象的类型。这非常的麻烦。为此要尽量避免在外部类和非静态方法之外实例化内部类对象。再者,内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象。简单的说,内部类对象与外部类对象之间的关系非常的紧密。有时候即像一对父子(成员内部类可以 随意使用外部类的成员),有时候又像是陌生人(外部类不能够直接使用内部类中的成员)。作为一个出现的程序开发人员,必须要深入了解外部类对象与内部类对象的关系。因为在实际工作中,在外部类中定义成员内部类还是很常见的。只有了解他们彼此之间的关系,在编写应用程序中,才可以把控好他们。

  • 4. 使用this关键字获取内部类与外部类对象的引用:

    • 在外部类和成员内部类中,都可以定义变量。成员内部类可以随意访问外部类中的变量,而外部类不能够直接访问内部类中的变量,只有通过使用内部类对象来引用内部类的成员变量。不过需要注意的是,在外部类中定义的变量与内部类中定义的变量名字可以相同。也就是说,在外部类中可以定义一个变量i,在内部类中也可以定义一个变量i。此时新的问题就出来了。由于在内部类中可以随意访问外部类中成员方法与成员变量。但是此时成员内部类与外部类的变量名相同,那么如果要在内部类使用这个变量i,编译器怎么知道现在需要调用外部类的变量i,还是内部类的变量i呢?

    • 在实际工作中,应该尽量避免这种情况。即在定义变量名字的时候,内部类中的变量名字尽量不要与外部类中的变量名字相同。但是如果真的发生这种情况的话,Java编译器还是有手段可以解决这个变量名字的冲突问题。如果在类中的成员内部类中遇到成员变量名字与外部类中的成员变量名字相同,则可以通过使用this关键字来加以区别。如现在有一个类名字叫做student。而在这个类中又创建了一个成员内部类,名字叫做age。现在在这两个类中各自定义了一个成员变量i,用来做循环之用。此时如果在成员内部类中调用这个变量,该如何进行区分呢?通常情况下,如果使用this.i的方式来调用变量,则表示这个变量是成员内部类中定义的变量。而通过使用student.this.i的形式调用变量时表示此时引用的是外部类的成员变量。也就是说,如果内部类中引用外部类中同名的成员,需要通过外部类迷名字.this.外部类成员名字的方式加以引用。而且,在引用成员内部类自己的定义的成员时也需要使用this关键字,以加以区别。显然这非常的麻烦。为此笔者仍然要强调一遍,除非有非常特殊的必要,否则的话要尽量避两外部类与成员内部类中成员变量或者成员方法采用相同的名字。否则的话,会给后续的引用带来很大的麻烦。

九、Reactor模式(学习Netty之前的准备)

参考博客链接:[Reactor模式](www.cnblogs.com/crazymakerc…)

十、多态的作用

  • 1.多态的概念:
    • 多态是同一个行为具有多个不同表现形式或形态的能力,是对象多种表现形式的体现。

  • 2.多态的优点:
      1. 消除类型之间的耦合关系;2. 可替换性;3. 可扩充性;4. 接口性;5. 灵活性;6. 简化性。
  • 3.多态存在的三个必要条件:
    • 继承、重写、父类引用指向子类对象

十一、 Class的isAssignableFrom方法详解

  • 类的Class实例中有一个isAssignableFrom方法,这个方法是用来判断两个类的之间的关联关系,也可以说是一个类是否可以被强制转换为另外一个实例对象,源码如下:
     /**
     * Determines if the class or interface represented by this
     * {@code Class} object is either the same as, or is a superclass or
     * superinterface of, the class or interface represented by the specified
     * {@code Class} parameter. It returns {@code true} if so;
     * otherwise it returns {@code false}. If this {@code Class}
     * object represents a primitive type, this method returns
     * {@code true} if the specified {@code Class} parameter is
     * exactly this {@code Class} object; otherwise it returns
     * {@code false}.
     *
     * <p> Specifically, this method tests whether the type represented by the
     * specified {@code Class} parameter can be converted to the type
     * represented by this {@code Class} object via an identity conversion
     * or via a widening reference conversion. See <em>The Java Language
     * Specification</em>, sections 5.1.1 and 5.1.4 , for details.
     *
     * @param cls the {@code Class} object to be checked
     * @return the {@code boolean} value indicating whether objects of the
     * type {@code cls} can be assigned to objects of this class
     * @exception NullPointerException if the specified Class parameter is
     *            null.
     * @since JDK1.1
     */
  public native boolean isAssignableFrom(Class<?> cls);
  • 源码是一个native方法,是使用c语言编写的,JDK方法的注解翻译后如下:

有两个Class类型的类象,一个是调用isAssignableFrom方法的类对象(后称对象A),以及方法中作为参数的这个类对象(称之为对象B),这两个对象如果满足以下条件则返回true,否则返回false:

  • 1.A对象所对应类信息是B对象所对应的类信息的父类或者是父接口,简单理解即A是B的父类或接口;
  • 2.A对象所对应类信息与B对象所对应的类信息相同,简单理解即A和B为同一个类或同一个接口;
    如下测试代码示例:
        Class clazz1 = Map.class;
        Class clazz2 = HashMap.class;
        Class clazz3 = List.class;
        boolean f = clazz1.isAssignableFrom(clazz2);
        System.out.println(f);//TRUE
        boolean f1 = clazz1.isAssignableFrom(clazz1);
        System.out.println(f1);//TRUE
        boolean f3 = clazz1.isAssignableFrom(clazz3);
        System.out.println(f3);//FALSE