核心类库阶段回顾

1.数组(Array) 和列表(ArrayList) 有什么区别?
  Array可以包含基本类型和对象类型,ArrayList只能包含对象类型

  Array大小固定,ArrayList的大小是动态变化的。

  ArrayList提供了更多的方法和特性:比如 :addAll(),removeAll(),iterator()等等。

  对于基本数据类型,集合使用自动装箱来减少编码工作量。但是,当处理固定大小基本数据类型的时候,这种方式相对较慢。

2.ArrayList 和 Vector 的区别
   这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的——这是由List集合规范制订的。

  而且ArrayList与Vector底层都是基于数组的,因此它们的实现代码也大致相似。区别在于Vector是一个古老的集合,从JDK1.0开始就有了,因此它包含了大量方法名很长的方法,JDK 1.2开始引入集合框架,引入List接口,才让Vector实现了List接口,因此又增加了一些List接口中定义的方法。总体来说,ArrayList可以完全代替Vector,除了在一些很古老的API中强制要求使用Vector之外。

  Vector还有一个特征:它是线程安全的,因此性能比较差。而ArrayList并不是线程安全的,因此性能较好。实际上即使我们要在多线程环境下使用List集合,也应该选择ArrayList,而不是Vector,因为Java还提供了一个Collections工具类,它可以把ArrayList包装成线程安全的集合类,例如如下代码:

1 List list = Collections.synchronizedList(new ArrayList());
3.HashMap,TreeMap,HashTable 的区别?
   Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。

  TreeMap是有序的,HashMap和HashTable是无序的。

  Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。

  这就意味着Hashtable是线程安全的,HashMap不是线程安全的。HashMap效率较高,Hashtable效率较低。 如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。

  Hashtable不允许null值,HashMap允许null值(key和value都允许)

  父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap

  Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。

  HashMap中hash数组的默认大小是16,而且一定是2的指数。

4.HashMap 的工作原理是什么?
   Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。
5.什么是序列化,如何实现序列化?
  序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
  序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
6.进程和线程有什么区别?
  根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
  在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

  所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

  内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

  包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

7.java 当中如何实现线程呢?
  在Java中线程的调度是抢占式调度,线程的实现常用的有两种方法
  1.继承Thread
  MyThread.java
1 public class MyThread extends Thread{
2     public void run(){
3         //run方法就是线程要执行任务的方法
4         //这是执行路径 触发方式不是调run而是用start()启动系统
5         for(int i=0;i<10;i++){
6             System.out.printIn("m线程"+i);
7         }
8     }
9 }

  Demo.java

 1 public class Demo{
 2     //多线程技术 抢占
 3     public static void main(String[] args){
 4         MyThread m=new MyThread();
 5         //启动m线程
 6         m.start();
 7         for(int i=0;i<10;i++){
 8             System.out.printIn("main'线程");
 9         }
10     }
11 }

程序启动 > main线程开启 > main方法执行 > 创建m对象 >
  1.m线程开启 循环十次
  2.main本身循环十次

  2.实现Runnable

  用于给线程执行任务

  MyRunnable.java

1 public class MyRunnable implements Runnable{
2     //线程的任务
3     public void run(){
4         for(int i=0;i<10;i++){
5             System.out.prinIn("m线程");
6         }
7     }
8 }

  Demo.java

 1 public class Demo{
 2     public static void main(String[] args){
 3         //1.实现Runnable 创建一个任务对象
 4         MyRunnable r=new MyRunnable();
 5         //2.创建一个线程并分配任务
 6         Thread t=new Thread(r);
 7         //3.执行这个线程
 8         t.start();
 9         for(int i=0;i<10;i++){
10             System.out.printIn("主线程");
11         }
12     }
13 }

执行顺序和上同

实现Runnable和继承Thread相比优势

  1. 通过创建任务给线程分配的方式,适合多线程同时执行情况。
  2. 避免单继承局限。
  3. 任务与线程分离提高程序健壮性。
  4. 线程池技术仅授权Runnable型任务,不接收Thread型。
8.说说线程的生命周期
  • New(初始化状态)
  • Runnable(可运行/运行状态)
  • Blocked(阻塞状态)
  • Waiting(无时间限制的等待状态)
  • Timed_Waiting(有时间限制的等待状态)
  • Terminated(终止状态)
9.多线程并发或线程安全问题如何解决?
  1:通过volatile 关键字修饰变量,可以实现线程之间的可见性,避免变量脏读的出现,底层是通过限制jvm指令的重排序来实现的适用于一个线程修改,多个线程读的场景
  2:通过synchronized锁(任意对象)来实现线程同步,自动锁的思想,底层实现原理:当有线程进入同步代码块之后,利用jvm的计数器将锁的标记置为1,当别的线程再想进入的时候,发现锁的标记为1,该线程就去锁池等待,当第一个线程出来之后,锁的标记会置为0,之后cpu会随机分配一个线程再次进入同步代码块.
  3:通过lock锁的机制,进行手动lock,和unlock,但是这种很容易出现死锁。注意加锁以及解锁的顺序,就可以避免死锁
  4:通过线程安全的集合类,可以解决并发问题4
    ConcurrentHashMap
    CopyonWriteArrayList
  5:使用并发包下面的原子类,底层使用的是cas机制(乐观锁),可以解决并发问题 atomicInteger 线程安全的原子整型类
  6:使用线程池来创建和管理线程,也可以一定程度上解决并发问题
  7:使用ThreadLocal来修饰变量,可以解决并发问题
  ThreadLocal底层是怎么实现的?
  多个线程会复制一份threadLocao变量的副本进行操作,互不影响,来保证线程安全的
10.synchronized 和 ReentrantLock 的区别
   功能区别:
    这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成
 
    便利性:很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
 
    锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized
  性能的区别:
    在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

实现Runnable

原文地址:https://www.cnblogs.com/zhangzhongkun/p/14483464.html