java核心技术卷一

时间:2022-06-02 16:17:49

java核心技术卷一



java基础类型

整型

数据类型 字节数 取值范围
int 4 +_2^4*8-1
short 2 +_2^2*8-1
long 8 +_2^8*8-1
byte 1 -128-127
     

浮点类型

数据类型 字节数 取值范围 小数位数
float 4 10^-38~10^38和-10^-38~-10^38 小数位数6-7
double 4 10^-308~10^308和-10^-308~-10^308 15位小数
       

boolean 类型和char 类型

java字符串

不可变字符串

    JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池,字符串常量池存
在于堆内存中的永久代。

1、StringBuilder与 StringBuffer:StringBuilder:线程非安全的,StringBuffer:线程安全的。
2、String.intern 方法

深入解析String#intern: http://tech.meituan.com/in_depth_understanding_string_intern.html

java自动装箱与对象包装器

    java中基础类型都有对应的包装器,装箱类型Integer,Long,Double,Short,Float,Byte,Character,Void,Boolean。

java集合

  • ArrayList 动态数组,动态的增加和缩减索引序列
  • LinkedList 链表集合,在任意位置高效的插入和删除的有序序列
  • ArrayDeque 用循环数组实现的双端队列
  • Hashset 无重复元素的无序列表
  • TreeSet
    有序集(二叉树)
  • EnumSet 枚举类型集
  • LinkedHashSet 有序的HashSet
  • HashMap 字典
  • TreeMap 有序映射表

java泛型

    java与c#一样,都存在泛型的概念,及类型的参数化。java中的泛型是在jdk5.0后出现的,但是java中的泛型与C#中的泛型是有本质区别的,首先从集合类型上来说,java 中的ArrayList<Integer>和ArrayList<String>是同一个类型,在编译时会执行类型擦除,及java中的类型是伪泛型,伪泛型将会在后面介绍,其次,对于像集合中添加基本类型的数据时,例如int,会首先将int转化成Integer对象,即我们通常所说的装箱操作,在取出元素的时候需要将Interger对象转换成int值类型,即拆箱操作。而在c#中,List<int>和List<string>是不同的类型,泛型参数在编译后会是一个占位符,并没有被擦除,在运行时被赋予正真的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀(针对类型膨胀,即时编译器已经做了很多的优化工作来解决这一问题),这就是所谓的真泛型。与此同时,在对集合中添加基本元素如int时,不需要装箱操作,取出元素时不需要拆箱操作,因此,性能上较java的集合泛型要好。
java中泛型的引入主要是为了解决两个方面的问题:1.集合类型元素在运行期出现类型装换异常,增加编译时类型的检查,2. 解决的时重复代码的编写,能够复用算法。下面通过例子来说明编译器的类型检查。

java多线程

java 线程的几种状态

  • New(新创建)

  • Runnable(可运行)

  • Blocked(被阻塞)

  • Waiting(等待) 不占用CPU时间

  • Timed waiting(计时等待)

  • Terminated(被终止)

      BLOCKED是指线程正在等待获取锁;WAITING是指线程正在等待其他线程发来的通知(notify),收到通知后,可能会顺序向后执行(RUNNABLE),也可能会再次获取锁,进而被阻塞住(BLOCKED)。
    

    一个线程new 出来处于new的状态,当线程调用start方法,线程处于runnable 状态,当线程试图获取其他线程独占的资源时,线程进入blocked阻塞状态,当线程shleep方法调用或者试图获取条件锁等待其他线程唤醒则处于等待(waiting)状态。

      线程终止有两种情况,一种是正常终止,比如调用Thread.join 方法,一种是异常终止,程序发生未处理的异常。
    

    请注意,Thread.stop() 方法已弃用,他会强制终止一个线程和子线程,可能造成数据未能同步或者资源未释放等异常。

java线程优先级

    java线程的优先级根据不同的虚拟机映射到不同不同的操作系统优先级,子线程继承主线程的优先级。

java 线程同步

    当竞态条件产生时,java有两种机制防止对象被并发访问的干扰。
  • ReentrantLock 公平锁 的lock(), unlock() 方法用于锁定和解锁代码片段。
  • Condition 条件锁 比如有界缓冲区的实现:
--------------------------------------------------------------------------------
    class BoundedBuffer {  
    final Lock lock = new ReentrantLock();//锁对象  
    final Condition notFull  = lock.newCondition();//写线程条件   
    final Condition notEmpty = lock.newCondition();//读线程条件   
    
    final Object[] items = new Object[100];//缓存队列  
    int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;  
    
    public void put(Object x) throws InterruptedException {  
        lock.lock();  
        try {  
        while (count == items.length)//如果队列满了   
            notFull.await();//阻塞写线程  
        items[putptr] = x;//赋值   
        if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0  
        ++count;//个数++  
        notEmpty.signal();//唤醒读线程  
        } finally {  
        lock.unlock();  
        }  
    }  
    
    public Object take() throws InterruptedException {  
        lock.lock();  
        try {  
        while (count == 0)//如果队列为空  
            notEmpty.await();//阻塞读线程  
        Object x = items[takeptr];//取值   
        if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0  
        --count;//个数--  
        notFull.signal();//唤醒写线程  
        return x;  
        } finally {  
        lock.unlock();  
        }  
    }   
    } 
--------------------------------------------------------------------------------
  • synchronized关键字

    公平锁和条件锁提供了相当细粒度的线程同步控制操作能力,在java1.0开始,每个对象都有一个内部锁,我们可以通过synchronized 关键字声明一个同步方法或者同步代码块。当然他也不能提供一些细粒度的操作能力,比如中断一个正在试图获取锁的线程, 设置锁的阻塞时长,单一的获得锁的条件等。

  • volatile 与易失构造

    1、多核处理器能够在寄存器或者本地内存缓冲区中保存内存的值,不同的处理器可能从内存中读到不同的值

    2、编译器根据优化场景可能改变指令的执行顺序,编译器假定内存的值只有在显示修改内存的指令执行时内存的值才发生改变,但是另一个线程 可能正在修改内存的值。

理解场景=> java 单例模式:

/**   
* 实现单例访问Kerrigan的第四次尝试   
*/    
public class SingletonKerriganD {     
    
    /**   
    * 单例对象实例   
    */    
    private static SingletonKerriganD instance = null;     
    
    public static SingletonKerriganD getInstance() {     
        if (instance == null) {     
            synchronized (SingletonKerriganD.class) {     
                if (instance == null) {     
                    instance = new SingletonKerriganD();     
                }     
            }     
        }     
        return instance;     
    }     
}    

我们来看看这个场景:假设线程一执行到instance = new SingletonKerriganD()这句,这里看起来是一句话,但实际上它并不是一个原子操作(原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。事实上高级语言里面非原子操作有很多,我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大致做了3件事情: 

1.给Kerrigan的实例分配内存。 

2.初始化Kerrigan的构造器。

3.将instance对象指向分配的内存空间(注意到这步instance就非null了)。 
但是,由于Java编译器允许处理器乱序执行(out-of-order),以及JDK1.5之前JMM(Java Memory Medel)中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是无法保证的,也就是说,执行顺序可能是1-2-3也可能是1-3-2,如果是后者,并且在3执行完毕、2未执行之前,被切换到线程二上,这时候instance因为已经在线程一内执行过了第三点,instance已经是非空了,所以线程二直接拿走instance,然后使用,然后顺理成章地报错,而且这种难以跟踪难以重现的错误估计调试上一星期都未必能找得出来,真是一茶几的杯具啊。 
DCL的写法来实现单例是很多技术书、教科书(包括基于JDK1.4以前版本的书籍)上推荐的写法,实际上是不完全正确的。的确在一些语言(譬如C语言)上DCL是可行的,取决于是否能保证2、3步的顺序。在JDK1.5之后,官方已经注意到这种问题,因此调整了JMM、具体化了volatile关键字,因此如果JDK是1.5或之后的版本,只需要将instance的定义改成“private volatile static SingletonKerriganD instance = null;”就可以保证每次都去instance都从主内存读取,就可以使用DCL的写法来完成单例模式。当然volatile或多或少也会影响到性能,最重要的是我们还要考虑JDK1.42以及之前的版本,所以本文中单例模式写法的改进还在继续。 
  • 无锁编程 并发环境下最常用的同步手段是互斥锁和读写锁,例如pthread_mutex和pthread_readwrite_lock,常用的范式为:

      void ConcurrencyOperation() {
          mutex.lock();
          // do something
          mutex.unlock();
      }
    

    这种方法的优点是:

    1、编程模型简单,如果小心控制上锁顺序,一般来说不会有死锁的问题;

    2、可以通过调节锁的粒度来调节性能。

缺点是:

  • 所有基于锁的算法都有死锁的可能;
  • 上锁和解锁时进程要从用户态切换到内核态,并可能伴随有线程的调度、上下文切换等,开销比较重;
  • 对共享数据的读与写之间会有互斥。

无锁编程(严格来讲是非阻塞编程)可以分为lock free和wait-free两种,下面是对它们的简单描述: lock free:锁无关,一个锁无关的程序能够确保它所有线程中至少有一个能够继续往下执行。这意味着有些线程可能会被任意的延迟,然而在每一个步骤中至少有一个线程能够执行下去。因此这个系统作为一个整体总是在前进的,尽管有些线程的进度可能没有其它线程走的快。 wait free:等待无关,一个等待无关的程序可以在有限步之内结束,而不管其它线程的相对执行速度如何。 lock based:基于锁,基于锁的程序无法提供上面的任何保证,任一线程持有了某互斥体并处于等待状态,那么其它想要获取同意互斥体的线程只有等待,所有基于锁的算法无法摆脱死锁的阴影。

lock free 一般是基于CAS(Compare And Swap)操作

CAS(void *ptr, Any oldValue, Any newValue);

即查看内存地址ptr处的值,如果为oldValue则将其改为newValue,并返回true,否则返回false。X86平台上的CAS操作一般是通过CPU的CMPXCHG指令来完成的。CPU在执行此指令时会首先锁住CPU总线,禁止其它核心对内存的访问,然后再查看或修改*ptr的值。简单的说CAS利用了CPU的硬件锁来实现对共享资源的串行使用。

它的优点是:

开销较小:不需要进入内核,不需要切换线程;
没有死锁:总线锁最长持续为一次read+write的时间;
只有写操作需要使用CAS,读操作与串行代码完全相同,可实现读写不互斥。

而在性能层面上,CAS与mutex/readwrite lock各有千秋,简述如下:

单线程下CAS的开销大约为10次加法操作,mutex的上锁+解锁大约为20次加法操作,而readwrite lock的开销则更大一些。 CAS的性能为固定值,而mutex则可以通过改变临界区的大小来调节性能; 如果临界区中真正的修改操作只占一小部分,那么用CAS可以获得更大的并发度。 多核CPU中线程调度成本较高,此时更适合用CAS。

无锁编程还可以通过减少锁范围等方式实现无锁或更轻量级的锁机制,参见java CurrenHhashMap实现:

http://blog.csdn.net/kjfcpua/article/details/11937817

  • 线程中断 java 中提供了interrupt方法中断线程,线程中断实际上是并非剥脱CPU运行时间片而获取的CPU中断,而是提供一个通知机制来告诉线程应该处理中断逻辑。

  • 线程池

java 线程池创建语法:

ExecutorService threadPool = Executors.newFixedThreadPool(3)

java JVM,GC

java NIO,netty