JAVA知识点,笔试面试基本点(2)

1.HashMap、HashTable以及ConCurrentHashMap有什么区别?

   先HashMap、HashTable比较。

(1)HashMap是非线程安全的,HashTable是线程安全的。
(2)HashMap的键和值都允许有null存在,而HashTable则都不行。

(3)因为线程安全、哈希效率的问题,HashMap效率比HashTable的要高。

 再HashTable以及ConCurrentHashMap比较

(1)Hashtable继承了Dictionary类,实现了Map接口,提供了clone方法。 

ConcurrentHashMap继承了AbstractMap类,实现了ConcurrentMap接口。 

(2)它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

2.volatile与synchronized的区别
首先要明白一点,对一些变量的写操作会先在寄存器或者是CPU缓存上进行,最后才写入内存.而在这个过程中,变量的新值对其他线程是不可见的。然后原子操作是一个不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束。

当对volatile标记的变量进行修改时,会将其他缓存中存储的修改前的变量清除,然后重新读取。这里从哪读取我并不明确,一般来说应该是先在进行修改的缓存A中修改为新值,然后通知其他缓存清除掉此变量,当其他缓存B中的线程读取此变量时,会向总线发送消息,这时存储新值的缓存A获取到消息,将新值穿给B。最后将新值写入内存。当变量需要更新时都是此步骤,volatile的作用是被其修饰的变量,每次更新时,都会刷新上述步骤。


1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性。《Java编程思想》上说,定义long或double变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作)原子性。
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
5、当一个域的值依赖于它之前的值时,volatile就无法工作了,如n=n+1,n++等。如果某个域的值受到其他域的值的限制,那么volatile也无法工作,如Range类的lower和upper边界,必须遵循lower<=upper的限制。
6、使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。

引用参考链接https://www.cnblogs.com/tf-Y/p/5266710.html。

3.JAVA中线程安全的集合类有哪些?

vector:比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,继承了Vector
hashtable:比hashmap多了个线程安全
enumeration:枚举,相当于迭代器

除了这些之外,其他的都是非线程安全的类和接口。比如常用的ArrayList、HashMap都是线程不安全的。

4.super关键字在构造方法中必须写在第一行吗?

在构造函数中,如同this一样,必须写在构造函数的第一行,而在成员函数中,则可以随意写在某一行。

5.ARM和AOM

RAM-RamdomAccessMemory随机存取存储器,如计算机内存等。类似于手机运行内存。
 

ROM-Read Only Memory只读存储器。断电后信息不丢失。存取速度很低,用户存储。

6.transient关键字用法

当用transient关键字修饰一个变量时,这个变量将不会参与序列化过程。也就是说它不会在网络操作时被传输,也不会再本地被存储下来,这对于保护一些敏感字段(密码)。

7.线程池ThreadPool

参数:

(1)corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
(2)maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
(3)keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
(4)TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS)等。
(5)workQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
(6)threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

(7)handler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

优点:

(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;

(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

处理流程:

1、首先线程池判断基本线程池是否已满(< corePoolSize ?)?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
2、其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。

3、最后线程池判断整个线程池是否已满(< maximumPoolSize ?)?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

8.sleep和wait的区别

sleep:保持锁,睡眠时间到进入就绪状态;

wait:释放锁,等待其他线程的notify操作或超时唤醒。

sleep是Thread的静态方法,wait是Object的方法。两个方法都会暂停当前线程
(1)sleep使当前线程阻塞,让出CPU,给其他线程执行的机会;如果当前线程拥有锁,不会释放锁,也即“睡着我也要拥有锁”。睡眠时间一到,进入就绪状态,如果当前CPU空闲,才会继续执行。
(2)wait方法调用后,当前线程进入阻塞状态,进入到和该对象(即谁调用了wait()方法,如list.wait())相关的等待池中。,让出CPU,给其他线程执行的机会;当超时间过了或者别的线程调用了notify()或notifyAll()方法时才会唤醒当前等待同一把锁的线程。

(3)wait方法必须要放在同步块中,如syncbronized或Lock同步中。

9.实现线程的两种方式和区别

采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

10.对象创建的4种方式(对象的创建一定会走构造函数吗)

1.使用new关键字

2.使用反射机制

3.使用clone方法

4.使用反序列化

前两种方法会通过构造函数,后两种不会。

猜你喜欢

转载自blog.csdn.net/qq_38679144/article/details/80415756