【Java面试】-- 杂题

杂题


2019-11-03  21:09:37  by冲冲

1、类加载器的双亲委派机制

类加载器:把类通过类加载器加载到JVM中,然后转换成class对象(通过类的全路径来找到这个类)。

双亲委派机制:当有一个类进入虚拟机加载环节后,但是他自己的类加载器不去加载,而是让其父类加载器加载(让上级加载器加载),只有当父类加载器找不到这个类的时候,子类加载器才会去加载.

优点:为了安全,Object是所有类的父类,系统类的加载都是经过Object加载器加载。此时,如果有一个自定义的类,但是类名和系统类中的某个类的类名相同,此时由于双亲委派机制的存在,JVM可以判定,哪一个是真的系统类,哪一个是自定义的的类(也就是通过类加载器判断)。可以避免黑客伪造破坏类。

2、HashSet如何去重?

① 首先调用hashCode()方法判断hashCode是否存在,如果不存在则无重复。

② 如果hashCode存在,则调用equals()方法判断有无对象内容重复,如果没有则不重复。

3、volatile关键字是否能保证线程安全?

不能。volatile关键字用在多线程同步中,可保证读取的可见性。JVM只是保证从主内存加载到线程工作内存的值是最新的读取值,而非cache中。但多个线程对volatile的写操作,无法保证线程安全。

假如,线程1和线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值,在线程1对count进行修改之后,会write到主内存中,主内存中的count变量就会变为6。线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6。

4、Java能不能不通过构造函数创建对象?

能。Java创建对象的4种方式:

(1) 用new语句创建对象,这是最常见的创建对象的方法。

(2) 运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。

(3) 调用对象的clone()方法。

(4) 运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。

注意:(1)和(2)都会明确的显式的调用构造函数 ;(3)是在内存上对已有对象的影印,所以不会调用构造函数 ;(4)是从文件中还原类的对象,也不会调用构造函数。

原型模式应用clone()

clone()是浅拷贝,可以重写成深拷贝。

浅拷贝:如果目标对象A,拥有一个引用类型的变量B,对A进行clone()得到复制品a,a也拥有引用类型变量b,a和b指向同一个对象(内存地址相同)。

深拷贝:a和b分别指向内存地址不同的两个同类型对象。

 (在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存)

5、结论

① 子类新增的方法是不能直接操作被子类隐藏的成员变量的。子类重写的方法可以直接操作被子类隐藏的成员变量的。

② 不允许使用static修饰abstract方法。

③ 内部类的类名只能在定义它的类或程序段中或在表达式内部匿名使用。

④ 内部类可以使用它所在类的静态成员变量和实例成员变量。

⑤ 内部类可以用abstract修饰符定义为抽象类。

⑥ 内部类可作为其他类的成员,而且可访问它所在类的成员。

⑦ abstract方法必须在abstract类中。

⑧ static方法中不能处理非static的数据。

6、MyBatis的一级缓存和二级缓存

(1)一级缓存:①默认开启。②作用范围是一个sqlSession。

(2)二级缓存:①默认不开启。②作用范围是一个mapper(namespace),包括多条sqlSession。

缓存的作用:Mybatis首先前往缓存中查询结果集,如果没有则查询数据库,如果有则从缓存取出返回结果集就不走数据库。

底层实现:Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句,value为从查询出来映射生成的java对象。

7、线程的创建方式(7种)

①继承Thread类

②实现Runable接口

③实现Callable接口和FutureTask类(该类实现的是Runable()接口)

④匿名Thread类

⑤Timer类和TimerTask类

⑥线程池Executors

⑦Stream类

(1)继承Thread类,作为线程对象存在

public class CreatThreadDemo1 extends Thread{
    /**
     * 构造方法: 继承父类方法的Thread(String name);方法
     * @param name
     */
    public CreatThreadDemo1(String name){
        super(name);
    }

    @Override
    public void run() {
        while (!interrupted()){
            System.out.println(getName()+"线程执行了...");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
        CreatThreadDemo1 d2 = new CreatThreadDemo1("second");

        d1.start();
        d2.start();

        d1.interrupt();  //中断第一个线程
    }
}

① interrupted()方法,用于判断该线程是否被中断。

② interrupt()方法,本意是中断线程,但是用于终止线程。终止线程已经不允许用stop方法,该方法不会释放占用的资源。

③ Thread.sleep(200); //线程休息2ms。静态方法,线程不释放占用的资源(对象锁),线程进入sleep类型的阻塞状态。

④ Object.wait(); //让线程进入等待,直到调用Object的notify()或者notifyAll()时,线程停止休眠。调用者是对象不是线程,该方法使线程释放锁,进入同步队列,当被notify()或notifyAll()才被唤醒进入等待地列。

⑤ Thread.yield(); //线程放弃CPU时间片,进入Runable状态(sleep和wait才会阻塞),将cpu时间片让给其他优先级相等或者更高的线程。但是OS的调度机制也有可能继续把时间片交给该线程(备受青睐没办法)。yield只是暂停执行,不会释放对象锁(同sleep)。

(2)实现Runable()接口,作为线程任务存在

public class CreatThreadDemo2 implements Runnable {
    @Override
    public void run() {
        while (true){
            System.out.println("线程执行了...");
        }
    }

    public static void main(String[] args) {
        //将线程任务传给线程对象
        Thread thread = new Thread(new CreatThreadDemo2());
        //启动线程
        thread.start();
    }
}

(3)匿名内部类创建线程对象

public class CreatThreadDemo3 extends Thread{
    public static void main(String[] args) {
        //创建无参线程对象
        new Thread(){
            @Override
            public void run() {
                System.out.println("线程执行了...");
            }
        }.start();
       //创建带线程任务的线程对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行了...");
            }
        }).start();
        //创建带线程任务并且重写run方法的线程对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable run 线程执行了...");
            }
        }){
            @Override
            public void run() {
                System.out.println("override run 线程执行了...");
            }
        }.start();
    }

}

因为Thread类本身也实现Runable()接口,那么到底运行刚实现的Runable()接口的run方法,还是运行Thread类本身的run()方法?

应该是Thread类的run()方法,因为该段代码相当于Thread类实现两个Runable()接口,从上往下依次执行,最终Thread类的run()方法覆盖掉前面实现的Runable()的run()方法。

(4)实现Callable()接口,创建带返回值的线程

public class CreatThreadDemo4 implements Callable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CreatThreadDemo4 demo4 = new CreatThreadDemo4();

        FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最终实现的是runnable接口

        Thread thread = new Thread(task);

        thread.start();

        System.out.println("我可以在这里做点别的业务逻辑...因为FutureTask是提前完成任务");
        //拿出线程执行的返回值
        Integer result = task.get();
        System.out.println("线程中运算的结果为:"+result);
    }

    //重写Callable接口的call方法
    @Override
    public Object call() throws Exception {
        int result = 1;
        System.out.println("业务逻辑计算中...");
        Thread.sleep(3000);
        return result;
    }
}

Callable()接口源码

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

返回指定泛型的call方法。然后调用FutureTask对象的get方法可以得到call方法的返回值。

(5)定时器Timer

public class CreatThreadDemo5 {

    public static void main(String[] args) {
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器线程执行了...");
            }
        },0,1000);   //延迟0,周期1s

    }
}

(6)线程池创建线程

public class CreatThreadDemo6 {
    public static void main(String[] args) {
        //创建一个具有10个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        long threadpoolUseTime = System.currentTimeMillis();
        for (int i = 0;i<10;i++){
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"线程执行了...");
                }
            });
        }
        long threadpoolUseTime1 = System.currentTimeMillis();
        System.out.println("多线程用时"+(threadpoolUseTime1-threadpoolUseTime));
        //销毁线程池
        threadPool.shutdown();
        threadpoolUseTime = System.currentTimeMillis();
    }

}

shutdown()方法,要求所有线程都运行完之后再销毁。

shutdownnow()方法,要求所有线程立即停止(即便任务还没做完也要停止)然后销毁。

(7)利用java8新特性Stream实现并发

public class CreatThreadDemo7 {
    public static void main(String[] args) {
        List<Integer> values = Arrays.asList(10,20,30,40);
        //parallel 平行的,并行的
        int result = values.parallelStream().mapToInt(p -> p*2).sum();
        System.out.println(result);
        //怎么证明它是并发处理呢?因为输出结果是无序的,比如200,30,10,40,20
        values.parallelStream().forEach(p-> System.out.println(p));
    }
}
原文地址:https://www.cnblogs.com/yadiel-cc/p/11789392.html