• java基础知识扫盲


    1、string为什么不可继承?

    答:因为string是用final修饰的不可变类,不可以被继承;

           为什么要用final修饰?

    答:安全,设计者将string设为共享的,子类不可以修改,因此设置为final;

           效率,共享的效率更高;

           StringStringBufferStringBuilder区别

    答:运行速度StringBuilder>StringBuffer>String,String为字符串常量,一旦创建对象后不可更改;而后者是可更改的;

           线程安全上StringBuffer是线程安全的,StringBuilder是非线程安全的。

    2、怎样减少线程的上下文切换?

    答:减少并发线程数,尽量使用无锁编程,CAS算法

           什么是CAS算法?

    答:compare and swap意思是先比较旧的预期值和原来内存中存储的值相等,再更改为目标值,是一种乐观锁,适用于读多写少的情况,效率较高,只有极少的情况会发生冲突

           CAS会有ABA问题应怎么解决?

    答:ABA问题是指旧的预期值被更改为和原来内存中存储的值相等,已经发生过一次更改,这种情况可以通过版本号(AtomicStampedReference)、时间戳来解决,比较最初获取的版本号和更改前获取的版本号是否一致,一致则更改,更改后都将版本号+1

    3、说下volatile关键字的作用?

    答:可见性:当多个线程共同访问同一变量,当其中一个线程修改了该变量,其他线程都能立刻看到修改的值

           有序性:防止指令重排序,程序执行顺序按当前代码顺序执行

           注:指令重排序不会影响单个线程的执行,但会影响多个线程并发执行的结果

           https://www.cnblogs.com/dolphin0520/p/3920373.html

    内存屏障会提供3个功能:

      1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

      2)它会强制将对缓存的修改操作立即写入主存;

      3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

          volatile可以保证原子性吗?

    答:不是,两个线程同时从内存中读取变量的值i=10到自己的工作内存,线程a和线程b同时做自增操作+1,写入工作内存,再写入主内存,两个线程分别自增一次,但主内存中的结果只增加1,因此volatile不保证原子性

    4、描述下java内存模型

    答:Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

    5、描述下happens-before原则(先行发生原则)

    答:Java内存模型具备一些先天的“有序性”

    • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
    • 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
    • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
    • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
    • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
    • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
    • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
    • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

    6、ThreadLocal 原理

    定义:线程局部变量,为每一个线程都提供了一份变量副本,每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响;

    实现:在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本,每个线程可能存在多个ThreadLocal。

    应用:采用了“以空间换时间”的方式,解决多线程数据共享问题,实际过程中应用到了数据库连接。

    问题:ThreadLocal有可能产生内存泄漏,因为map中的key弱引用指向ThreadLocal,当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用。

    7、synchronized锁怎么实现的?

    答:synchronized锁存在Java对象头里,每一个被锁住的对象都会和一个monitor对象关联;

           执行monitorenter获取锁,读取monitor对象的count字段,count==0,则获取锁成功且count+1;

           执行monitorexit指令时将count-1,若count==0,则释放锁;

    synchronized为什么是重量级锁?

    答:重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。

    8、Lock锁怎么实现的?

    ReentrantLock是基于AQS实现的,AQS的基础是CAS

    AbstractQueuedSynchronizer,简称AQS,基于volatile int state +FIFO队列(双端队列)实现的,

    ReentrantLock中有一个抽象类Sync根据传入构造方法的布尔型参数实例化出Sync的实现类FairSync和NonfairSync,并重写tryAcquire(int arg)和tryRelease(int arg)两个方法。

    lock()利用CAS去判断state是不是0,如果state=0,则获取锁,state设为1并将当前线程设为主;

    如果state非0,执行acquire()->tryAcquire()->(非公平锁)如果state=0,CAS尝试获取锁,若加锁成功state设为1并将当前线程设为主,

                                                                                             state非0,判断当前owner是否是当前线程,如果是,state+1,返回true,如果不属于当前线程,返回false,则添加FIFO等待队列队尾。

                                                                        (公平锁)如果state=0,hasQueuedPredecessors()判断当前是否有等待的线程, 如果没有使用CAS尝试获取锁,若加锁成功state设为1并将当前线程设为主,

                                                                                         state非0,判断当前owner是否是当前线程,如果是,state+1,返回true,如果不属于当前线程,返回false,则添加FIFO等待队列队尾。

    ReentrantLock获取锁定与三种方式:

        a)  lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁;

        b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

        c)tryLock(long timeout,TimeUnit unit),如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

        d)lockInterruptibly():如果获取了锁定立即返回,如果没有获取锁,当前线程处于休眠状态,直到获取锁,或者当前线程被别的线程中断;

    9、synchronizedLock什么区别?

    类别synchronizedLock
    存在层次 Java的关键字,在jvm层面上 通过代码实现,是一个类
    锁的释放 会自动释放锁定(代码执行完成或者出现异常) 不会主动释放,必须将unLock()放到finally{}中
    锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
    锁状态 无法判断 可以判断
    锁类型 可重入,不可中断,非公平 可重入,可中断,可公平(两者皆可)
    性能 少量同步 大量同步

    10、线程池有哪几种?

    答:Java通过Executors提供四种线程池,分别为:

    1、newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制)SynchronousQueue

    2、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。LinkedBlockingQueue

    3、newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。

    4、newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。LinkedBlockingQueue

           线程池参数有哪些?

    答:ThreadPoolExecutor构造方法

    public ThreadPoolExecutor(int corePoolSize,//核心线程池大小

                                  int maximumPoolSize,//最大线程池大小

                                  long keepAliveTime,//线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)成为核心线程的有效时间

                                  TimeUnit unit,//keepAliveTime的时间单位

                                  BlockingQueue<Runnable> workQueue,//阻塞任务队列

                                  ThreadFactory threadFactory,//线程工厂

                                  RejectedExecutionHandler handler) {//当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理

          线程池的工作过程?

    答:1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程;

    2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行;

    3.workQueue已满,且当前线程数大于corePoolSize且小于maximumPoolSize时,新提交任务会创建新线程执行任务;

    4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理;

    5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程;

    6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭;

      线程池拒绝策略有几种?

    答:ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

      线程池相关的几个队列:

    答:ArrayBlockingQueue, 一个由数组结构组成的有界阻塞队列fifo;默认情况下不保证公平阻塞,可以通过配置创建公平阻塞的队列,实现原理是可重入锁。

    LinkedBlockingQueue,一个由链表结构组成的有界阻塞队列 fifo,LinkedBlockingQueue 可无界,也可以有界,初始化指定长度则为有界,不指定则为无界

    PriorityBlockingQueue,一个支持优先级排序的无界阻塞队列

    synchronousQueue,一个不存储元素的阻塞队列

    11、线程状态有几种?

    答:线程的生命周期为5个状态,新建状态、就绪状态(start)、运行状态(run)、阻塞状态和死亡状态。

    运行的线程执行sleep线程进入阻塞状态,sleep时间到了以后进入就绪状态。

    线程的实现由3种方法:继承Tread类;实现Runnable接口;实现Callable接口

    Sleep和wait的区别:sleep属于Thread类,wait属于Object类,

                                   sleep线程不会释放对象锁,wait会释放对象锁;

                                   wait需要调用notify()方法后本线程才进入对象索定池(只能同步方法或同步代码块中使用),sleep结束后进入就绪状态。

    notify()与notifyAll()的区别:notify()随机通知一个线程;

                                                  notifyAll()通知所有线程;

    11、Java8新特性

    答:(1) lambda表达式 (2) Stream,处理集合数据(3) 接口default方法,有利于数据移植

    12、抽象类和接口

    答:抽象类和抽象方法都需要被abstract修饰,抽象方法一定要定义在抽象类中。抽象类所有方法都是抽象方法的时候,完全可以用接口实现。抽象类为部分方法提供实现,避免了子类重复实现这些方法,提供了代码重用性。单继承,多实现。既要定义子类的行为,又要为子类提供共性功能时才选用抽象类。

    13、内部类

    静态内部类:static修饰,可以访问外部类所有的静态变量和方法。应用场景如hashmap的静态内部类entry

    成员内部类:可以访问外部类所有的变量和方法。和静态内部类不同,成员内部类的实例都依赖外部类的实例。

    局部内部类:即定义在方法中的类,可以访问外部类的所有变量和方法,如果局部内部类用static修饰方法内,则只能访问外部类的static变量

    匿名内部类:匿名内部类可以出现在任何允许表达式出现的地方,定义格式为new 类/接口。应用场景,分布式工具类,多线程,android中的绑定监听事件。

    13、反射

    反射是java在运行状态下,对于任意一个类,都能够知道这个类的所有属性和方法;

    优点是可以动态创建对象和编译;缺点是对性能有影响。

    获取方式有3种:类名.class,Class.forName(xxx); new 类名.getclass()。

    反射的应用场景有:反编译、mybatis底层实现、工具类,bean转map,通过反射获取注解,可以做一些校验

    14、java面向对象的三大特性

      继承:是对有共同特性的多类事物,进行再抽象成一个类。java继承是extends关键字,子类中有和父类中可访问的同名同返回同参数列表的方法时,就会覆盖从父类继承来的方法

      封装:隐藏对象的属性和实现细节,仅对外提供接口。java中的类属性访问权限不是private,要想隐藏该类方法需要用private修饰符

      多态:两种机制:编译时多态和运行时多态.

      方法重载,方法名相同,其他不同,编译时重载。方法重写(覆盖),子类覆盖父类的方法。

    15、泛型

    泛型只在编译阶段有效,在编译过程中,正确检测泛型结果后,会将泛型的相关信息擦除,并且添加类型检测和类型转换。

    使用泛型的好处是:类型安全;消除强制类型转换,提高性能。

    泛型的具体使用有3种方式:

    (1) 泛型类,例如hashmap<K,V>,其中key和value只在运行时才会真正根据类型来构造和分配内存

    (2) 泛型接口,当2个接口的处理方式一致,只是对象不同的时候,可以用泛型接口

    (3) 泛型方法,把方法参数泛型话

    16、join是怎么实现的?

    join是thread类synchronized修饰的同步方法,其使用wait()方法实现等待;

     17、异常

    一、error

    程序无法处理的错误,比较严重的问题,虚拟机错误,如OutOfMemoryError、NoClassDefFoundError

    二、exception

    1、已检查异常

    编译器要求必须对代码try catch或者throw expetion,如IOException、SQLException、FileNotFoundException、ParseException

    2、未检查异常(RuntimeException运行时异常)

    编译时不检查,运行时可能发生的异常,如NullPointerException,IndexOutOfBoundsException

    18、有return的情况下try catch finally的执行顺序:

     (1)不管有没有出现异常,当执行到try内时,finally块中代码都会执行;
     (2)当try和catch中有return时,finally仍然会执行;
      (3)finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变(对象类型需要另外分析),仍然是之前保存的值),所以函数返回值是在finally执行前确定的;
      (4)finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

    19、对象的初始化顺序:

    (1)类加载之后,按从上到下(从父类到子类)执行被static修饰的语句;

    (2)当static语句执行完之后,再执行main方法;

    (3)如果有语句new了自身的对象,将从上到下执行构造代码块、构造器(两者可以说绑定在一起)。

    20、cookie与session

    Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。要跟踪该会话,必须引入一种机制。

    Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

    Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。

    Cookie具有不可跨域名性。

    ookie生命周期默认为浏览器会话期间,驻留内存,关闭浏览器cookie就没了
    设置cookie的过期时间为永不过期,将cookie保存在硬盘上

    首先浏览器请求服务器访问web站点时,程序需要为客户端的请求创建一个session的时候,服务器首先会检查这个客户端请求是否已经包含了一个session标识、称为SESSIONID,如果已经包含了一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用,如果客户端请求不包含session id,则服务器为此客户端创建一个session并且生成一个与此session相关联的session id,sessionid 的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个sessionid将在本次响应中返回到客户端保存,保存这个sessionid的方式就可以是cookie,这样在交互的过程中,浏览器可以自动的按照规则把这个标识发回给服务器,服务器根据这个sessionid就可以找得到对应的session,又回到了这段文字的开始

    关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,一般是30分

    当浏览器将cookie禁用,基于cookie的session将不能正常工作,每次使用request.getSession() 都将创建一个新的session。达不到session共享数据的目的,但是我们知道原理,只需要将session id 传递给服务器session就可以正常工作的。

    解决:通过URL将session id 传递给服务器:URL重写

    21、通信方式

    一、进程间的通信方式
    # 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
    # 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
    # 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    # 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    # 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    # 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
    # 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。


    二、线程间的通信方式

    # 锁机制:包括互斥锁、条件变量、读写锁
    *互斥锁提供了以排他方式防止数据结构被并发修改的方法。
    *读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
    *条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
    # 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
    # 信号机制(Signal):类似进程间的信号处理
    线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

    22、使用DateTimeFormatter

    如果是Java8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类。

    23、BigDecimal 的等值比较应该要使用 compareTo() 方法,而不是 equals()方法。因为 equals() 会比较值和精度,而 compareTo() 会忽略精度

  • 相关阅读:
    Day01-基础加强笔记
    CS Academy Round#2 E.Matrix Coloring
    AtCoder ABC198 F
    NERC2021 B. Button Lock
    AtCoder ARC115 E
    NOI Online 2021 Round 1 提高组 愤怒的小 N
    洛谷 P6918 [ICPC2016 WF]Branch Assignment
    AtCoder ARC076 F
    Atcoder ABC155 F
    POJ 1966 Cable TV Network
  • 原文地址:https://www.cnblogs.com/tilamisu007/p/9554027.html
Copyright © 2020-2023  润新知