Java工程师面试1000题61-70

61、Map中的key和value可以为null吗?

HashMap对象的key和value均可以为null;

HashTable对象的key和value均不可以为null。

且两者的key值均不能重复,若添加key相同的键值对后,后面的value会自动覆盖前面的value,但是不会报错。

62、什么是线程互斥和线程同步?

在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源的时候,比如多个线程急用同一台打印机,会使打印结果相互交织在一起,难于区分;当多个线程急用共享变量、表格、链表时,可能会导致数据处理出错,因此线程同步的任务就是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序具有可重现性。

当线程并发执行时,由于资源共享和线程协作,会使线程之间存在以下两种制约关系:

间接相互制约:一个系统中的多个线程必然要共享某种系统资源,比如共享CPU、I/O设备等,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A再使用打印机时,其他线程都要等待。

直接相互制约:这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。

间接相互制约可以称为互斥,直接相互制约可以称为同步。对于互斥可以这样理解,线程A和线程B互斥的访问某个资源时,则他们之间就一定会产生个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程A操作完毕,这其实就是线程的同步了。

总结:

1.互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法制访问者对资源的访问顺序,即访问是无序的。
2.同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
3.同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。
4.互斥是一种特殊的同步。

63、编程实现:子线程运行10次后主线程再执行5次,交替执行三遍。

package InternetPracite;

/**
 * @author
 * @desc  编程实现:子线程运行10次后主线程再执行5次,交替执行三遍。
 * @create 2019-03-08 19:44
 */
public class TestMutiThread {
 
    public static void main(String[] args) {
 
        final Bussiness bussiness = new Bussiness();
        //子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    bussiness.subMethod();
                }
            }
        }).start();
 
        //主线程
        for (int i = 0; i < 3; i++) {
            bussiness.mainMethod();
        }
    }
 
    static class Bussiness{
 
        private boolean subFlag = true;
 
        public synchronized void mainMethod(){
            while (subFlag){
                try {
                    wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": main thread running loop count --" + i);
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            subFlag = true;
            notify();
        }
 
        public synchronized void subMethod(){
            while (!subFlag){
                try {
                    wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ": sub thread running loop count --" + i);
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            subFlag = false;
            notify();
        }
    }
}

64、怎么理解ThreadLocal?

ThreadLocal是一个关于创建局部变量的类,通常情况下,我们创建的变量是可以被任何一个线程访问并修改的,而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。ThreadLocal并不是为了解决线程安全问题,而是提供了一种将实例绑定到当前线程的机制,类似于隔离的效果,实际上自己在方法中new出来变量也能达到类似的效果。ThreadLocal和线程安全基本上不搭边,绑定上去的实例也不是多线程公用的,而是每个线程new一份,如果共用了,那就会引发线程安全问题。

既然每个访问 ThreadLocal 变量的线程都有自己的一个“本地”实例副本。一个可能的方案是 ThreadLocal 维护一个 Map,键是 Thread,值是它在该 Thread 内的实例。线程通过该 ThreadLocal 的 get() 方案获取实例时,只需要以线程为键,从 Map 中找出对应的实例即可。每个新线程访问该 ThreadLocal 时,需要向 Map 中添加一个映射,而每个线程结束时,应该清除该映射。

总结

  • ThreadLocal 并不解决线程间共享数据的问题
  • ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map
    由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal
    对象无法被回收的问题
  • ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry
    对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
  • ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景

65、说说进程、线程之间的区别。

简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程.进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高.线程是进程的一个实体,是cpu调度和分派的基本单位,是比进程更小的能独立运行的基本单位.同一进程中的多个线程之间可以并发执行。

66、创建两种线程的方式?他们有什么区别?

通过实现java.lang.Runnable接口或者通过继承java.lang.Thread类.相比继承Thread类,实现Runnable接口可能更优.原因有二:

  1. Java不支持多继承.因此继承Thread类就代表这个子类不能再继承其他类了.而实现Runnable接口的类还可能扩展另一个类.
  2. 类可能只要求可执行即可,因此继承整个Thread类的开销过大。

67、说一下线程的五种状态。

线程的五种状态:创建、就绪、运行、阻塞和死亡。
第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。

68、wait()和sleep()方法的区别。

sleep方法来自Thread类,wait方法来自Object类,调用sleep()方法的过程中,线程不会释放对象锁,而调用wait()方法的时候,线程会释放对象锁。sleep()睡眠后不出让系统资源,wait()方法会让出系统资源,可以让其他线程占用CPU;sleep()里面可以传入一个参数来指定睡眠时间,时间一到就会自动唤醒,而wait()需要配合notify()或者notifyAll()使用。

69、什么是线程池? 为什么要使用它?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

70、Java中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

原文:https://blog.csdn.net/qq_21583077/article/details/88355882

猜你喜欢

转载自blog.csdn.net/Mr_Quinn/article/details/88652057