Java基础

1.为啥JDKPorxy只能代理接口?

        1.1)为什么JDK代理要基于接口而不是类实现

               由于jdk在产生代理对象时会默认继承Porxy道理对象并实现xxSservice接口,Java中仅支持单继承所以jdk动态代理只能通过接口实现

        1.2)JDK代理中,在目标方法的内部调用另外一个方法目标方法,另一个方法执行时,为什么不经过代理对象。

                内部调用方法相当于是this调用,this指代当前对象并非代理对象,所以不会走代理类

     JDK代理的实现步骤:

  1. 创建一个接口以及接口的实现类
  2. 创建一个InvocationHandler以及实现类
  3. 通过Porxy的newInstance方法创建一个代理对象并通过该对象执行目标对象的方法
public interface UserService {
    int insert();

    int insert(int i);

    String query();
}
public class UserServiceImpl implements UserService {
    @Override
    public int insert() {
        System.out.println("insert");
        return 0;
    }

    @Override
    public int insert(int i) {
        System.out.println("insert"+i);
        return 0;
    }

    @Override
    public String query() {
        System.out.println("query");
        return null;
    }
}
public class UserServiceInvocationHandler implements InvocationHandler {


    //目标对象,为了让InvocationHandler对象持有目标对象的引用
    private Object target;

    //初始化target
    public UserServiceInvocationHandler(Object target){
        this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Invocation Handler");
        //这里的invoke是通过目标对象类型以及参数类型匹配找到目标方法的
        //target:调用的方法的对象
        //args:调用方法的参数
        return method.invoke(target,args);
    }
 SpringApplication.run(NoticeApplication.class, args);


        //创建类加载器
        ClassLoader loader = NoticeApplication.class.getClassLoader();

        //创建代理对象要实现的接口,这里指为UserServiceImpl创建代理对象,需指定代理对象要实现的接口
        Class [] c=new Class[]{UserService.class};

        //初始化目标对象
        InvocationHandler invocationHandler=new UserServiceInvocationHandler(new UserServiceImpl());

        //创建代理对象
        UserService userService = (UserService)Proxy.newProxyInstance(loader, c, invocationHandler);

        //执行目标方法
        userService.insert(1);

 执行结果,从中可以看到通过代理为UserService对象进行了增强

 这个说明在目标方法内部调用方法不会走代理类

 详情看这个:为什么JDK动态代理只能代理接口,不能直接代理类?_小手u的博客-CSDN博客_为什么jdk动态代理只能代理接口

2.为什么ArrayList查询快改动慢?

答:

        查询快:因为ArrayList底层是基于数组实现的,查询时通过索引定位数据,所以查询快。

        改动慢:因为数组中的数据都是连续的,如果不按照顺序进行增删改操作就会造成数据前后移动所以效率较慢。

插入数据时还会对数组大小进行判断,如果容量不够时会进行扩容,这个也是个耗时的操作。

3.ArrayList<String> list=newArrayList<>()当前list的容量是多少?

答:是0

       因为jdk1.8采用懒加载机制,第一次创建数组时没有指定大小,默认是{},就是个空数组,容量为0不是10,只有第一次添加元素时才会扩容

指定容量,底层会指定创建指定大小的数组容量为指定大小的数组

 @Test
    void t2(){
        List<String> list = new ArrayList<>(5);
//        list.add("dcs");
        try {
            Class<? extends List> c = list.getClass();
            Field data = c.getDeclaredField("elementData");
            data.setAccessible(true);
            int length = ((Object[]) data.get(list)).length;
            System.out.println(length);

        }catch (Exception e){
            e.printStackTrace();
        }

    }

第一次添加元素底层会直接创建默认容量为10的数组大小

 @Test
    void t2(){
        List<String> list = new ArrayList<>();
       list.add("dcs");
        try {
            Class<? extends List> c = list.getClass();
            Field data = c.getDeclaredField("elementData");
            data.setAccessible(true);
            int length = ((Object[]) data.get(list)).length;
            System.out.println(length);

        }catch (Exception e){
            e.printStackTrace();
        }

    }

4.数组的四种拷贝方法:

  1. 通过Arrays.capyof(拷贝的数组,新数组的长度)
     int [] arr1={1,2,3};
     int [] arr2= Arrays.copyOf(arr1,5);

           若拷贝的长度小于元数组,进截取指定的长度内元素,若大于则多余的位置通过默认值补齐

            

      2.通过system.arraycopy(源数组,源数组起始位置,目标数组,目标数组起始位置,复制长度)

int [] arr1={1,2,3};
int [] arr2=new int[4];
System.arraycopy(arr1,0,arr2,0,arr1.length);

  目标数组需要指定长度,并且拷贝的长度<=目标数组长度,否则会报下标越界异常

     3.通过Object的clone方法实现

int [] arr1={1,2,3,55};
int[] arr2 = arr1.clone();

    4.通过Arrays.copyOfRange(源数组,起止位置,结束位置)

int [] arr1={1,2,3,55};
int[] arr2 = Arrays.copyOfRange(arr1, 0, 2);

5.JVM类加载器有几种?

      类加载器、扩展加载器、应用程序加载器、自定义加载器

6.ArrayList是并发安全的么?你知道怎么将它变成安全的方式有几种?

答:如果使用现成的解决方案的话可以用:

        Vector:保证了数据安全但是执行效率低,已经很少使用了

        使用CollectionssynchronizedList(new ArrayList<>()),在使用set、add、remove方法时使用synchronized锁执行,既满足了线程安全又包含了操作集合的常用方法

        CopyOnWriteArrayList:在添加元素时会先将原list类表中的元素复制一份再添加,新集合的长度在原集合的基础上+1(先获取原集合,原长度,加锁,复制,释放锁)

        CopyOnWriteArraySet:通过addIfAbsent方法进行去重,添加元素时会判断是否存在,不存在则添加;

注:CopyOnWriteArrayListCopyOnWriteArraySet这两个适用于读多写少,由于每次写操作都会进行集合内存复制,会消耗性能,如果集合较大,容易造成内存溢出。

        使用ThreadLocal变量的initialValue()返回一个List实例:

 List list= (List)new ThreadLocal() {
            @Override
            protected List initialValue() {
                return new ArrayList();
            }
        }.get();
        list.add("fsd");
        String s=(String)list.get(0);
        System.out.println(s);

         使用synchronized关键字为程序加锁

7.并发和并行的区别?

        并发:在一个进程中执行多个任务

                  在一台处理器处理多个任务。

                  在同一时间间隔完成多个操作。      

        并行:在多个进程间执行多个任务。

                  在多台处理器上处理多个任务。

                  在同一时间内完成多个操作。

8.异步和阻塞的含义

        异步、同步:一件事,如果可以多人共同协作进行,这就是异步,不能共同进行先按顺序执行就是同步。

        阻塞、非阻塞:对于多件事,一个人,假如一个人做某件事被卡住了,如果可以换做的事,呢么这就是非阻塞,若不能就是阻塞。

9.如何启动一个jar应用?

        1)以命令的方式启动:Java -jar xxx.jar(一般不用)

        特点:当前当前ssh窗口被锁定,可用Ctrl+C打断程序的运行。

        2)Java -jar xxx.jar &

        特点:当前ssh窗口不被锁定,窗口关闭时程序终止。

        3)nohup java -jar xxx.jar  >file.txt &

        特点:将内容输出到该文件而不是控制台

        4)nohup java -jar xxx.jar >dev/null 2>&1 &

        特点:不做任何输出,应用在后台运行

        5)Java -jar xxx.jar --server.port=8888 

        基于端口号启动

10.进程与线程的关系

        1.)进程是资源分配的最小单位;线程是程序执行的最小单位

        2.)一个进程中有多个线程,至少有一个线程,并且一个进程内的所有线程共享同一个进程资源。

        3)进程关闭,内部的线程也就关闭了,但是线程关闭,进程不一定关闭

11.描述一下一个线程的生命周期

        线程的状态:新建、就绪、运行、阻塞、销毁

        1.)通过new关键字创建一个Thread线程实例将进入新建状态,此时线程未启动。

        2.)当调用当前线程的start()等待操作系统调度获取CPU时间片时将进入到就绪状态

        3.)倘若成功获得CPU时间片时进入运行状态并执行run()里面的逻辑。

        4.)如果没有wait()sleep()以及异常等因素的干扰成功执行结束时会对该线程对象进行释放则进入死亡状态。

        5.)如果在运行状态中遇到sleep()wait()时线程将进入到阻塞状态,需要等待超时唤醒或者其他线程携带同一对象的notify()或者notifyAll()方法唤醒当前阻塞线程,唤醒后不会立即执行,会进入就绪状态重新等待CPU分配时间片,拿到执行权后再次进入到运行状态,没有异常或者阻塞时就会进入死亡状态

12.wait与sleep的区别?

        1.sleep属于Thread中的静态方法,通过Thread.sleep()调用,需处理异常,并且不会释放锁,待阻塞时间过了可自行解除阻塞状态,可以在任意地方使用。

        2.wait属于Object中的方法,会释放锁,不需要处理异常,需要通过其他线程调用notify()或notifyAll()方法唤醒阻塞线程,只能在同步块或同步方法中使用。

13.如果异步线程有返回值,怎么做?

        通过Future类型来接收返回值由它的子类AsyncResult对象封装返回结果,这是个接口和实现类FutureTask都是代表异步计算的结果。

        Future是异步计算结果的容器接口,提供了以下几个功能:

                1.等待异步计算完成时检查计算结果是否完成。

                2.在异步计算完成时,获取计算结果。

                3.在异步计算完成前取消。

 使用@Async注解描述一个方法时,它的返回值就是void和Future两种,别的类型都会被当做null返回,这也就是为什么调用方调用异步方法时会出现空指针异常的原因。

注:被@Async注解描述的方法不能在本类中调用,否则异步会失效,一定是在别的类中调用,并且这个类需交给spring管理。

由于Runnable在执行完线程后无法获取现成的执行结果,这时Collable就弥补了这个漏洞,但是Collable无法作为任务提交到Thread中去,这时候需要通过一个“中间人”来连接,FutureTask就是这个中间人,它实现了RunnableFuture,继承了Runnable、Future,具有了返回值和作为Thread任务参数的能力。

    14.Java如何将异步转为同步的?

        也就是说将异步调用通过某种方式阻塞至返回结果

        1.)使用wait()或sleep()

        2.)使用条件锁

        3.)Future

        4.)使用CountDownLatch这个类允许一个或多个线程等待其他线程执行完后执行

15.如何创建一个线程池?

        1.)创建任务对象(实现Runnable接口),用于执行具体的任务逻辑。

        2.)通过Executors或ThreadPoolExecutor创建线程池。

        3.)将待执行的任务对象交给线程池执行。

        4.)关闭线程池(shutdown、shutdownNow)。

注:这种创建方式不建议使用,

创建线程池的方式

        1.)newCachedThreadPool():创建一个缓存线程池,线程数无限制,有空闲就直接用,没有就创建,在池中60s内未使用就销毁。

        2.)newFixedThreadPool(int):定长线程池,可控制线程最大并发数(同时执行的线程数),超出的线程会在队列中等待,int参数表述线程池中线程的数量。

        3.)newScheduledThreadPool():定长线程池,支持定时及周期性任务执行。

        4.)newSingleThreadExecutor():单线程化线程池,有且仅有一个工作线程执行任务。

16.如果使用@AutoWired注入一个由spring管理的多个实现类怎么解决?

        由于Autowired默认是通过类型来查找Bean的,如果有多个实现类或通过名字查找,默认是通过服务名的小写作为Bean的名字。

        1.通过指定Bean的名字来注入

        

         2.通过添加@Qualifier()指定服务名

       

 17.LinkedList的扩容机制是什么?最大能存多少?

        没有扩容机制,因为不是连续的数组,不需要指定大小。

        由于没有指定范围,理想上是无现存,取决于堆的容量。

18.HashMap中扩容机制由谁决定?

        size > 数组容量*加载因子触发扩容

        

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值