java面试题第1期(基础)

58 阅读3分钟

1、面向对象有哪些特征?

面向对象特征封装、继承、多态。

封装:隐藏对象的属性,将这些属性进行封装隐藏

/**
 * 封装
 */
public Student(String name, int age) {
    this.name = name;
    this.age = age;
}

继承:通过extends子类继承父类

public class Student extends People {
    public Student(String name, int age) {
        super(name, age);
    }
}

多态:多态就是多种状态,就是一个方法在这个类中是这个状态,在另一个类中是另一种状态。

/**
* 多态1
*/
public class User implements Action {
    @Override
    public void eat() {
        System.out.println("吃的状态一");
    }
}
/**
* 多态2
*/
public class Student implements Action{
    @Override
    public void eat() {
        System.out.println("吃的状态二");
    }
}

扩展:编程语言分为面向机器、面向过程、面向对象;面向过程:按解决问题的步骤,然后一步一步的实现,按本意就是按问题的过程顺序执行。

2、Arraylist与Linkedlist的区别?

1、Arraylist与Linkedlist都实现List接口。

2、Arraylist是基于数组、Linkedlist是基于双向链表。

3、对于随机访问:Arraylist优于Linkedlist,根据索引访问。

4、对新增和删除:Linkedlist代码Arraylist,Linkedlist只需要更换指针指向,Arraylist会移动数据与重新计算长度。

5、Linkedlist比Arraylist更占内存

3、什么是线程不安全?

先看如下例子

// 例子一
static int runNum = 0;
static final int TOTAL = 10000000;
for (int i = 0; i < TOTAL; i++) {
    runNum++;
}
for (int i = 0; i < TOTAL; i++) {
    runNum--;
}

System.out.println("输出结果为="+runNum);

输出结果为:

输出结果为=0
// 例子二
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < TOTAL; i++) {
            runNum++;
        }
    }
});
Thread thread2 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < TOTAL; i++) {
            runNum--;
        }
    }
});
thread.start();
thread2.start();

try {
    thread.join();
    thread2.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.println("输出结果为="+runNum);

输出结果为:

输出结果为=5835732

​ 通过上面的例子你会发现,“例一”结果是唯一的,但是“例二”得到的结果每次都不一样,这个是什么原因造成的呢?这就是线程不安全。那这个代码如何调整呢?看如下,其它就是增加一个“synchronized”

public class Student {
    static int runNum = 0;
    static final int TOTAL = 10000000;

    static synchronized void add() {
        runNum++;
    }

    static synchronized void reduce() {
        runNum--;
    }

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < TOTAL; i++) {
                    add();
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < TOTAL; i++) {
                    reduce();
                }
            }
        });
        thread.start();
        thread2.start();

        try {
            thread.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("输出结果为=" + runNum);
    }
}

4、影响线程不安全的原因?

​ 线程不安全主要有三个原因,原子性、可见性、指令重排。

​ 原子性:对于高级程序而言,对于一个简单的语句如a++,都会涉及多个指令(读取a的值->a+1的操作->赋值)才能完成a++的操作。但是CPU的执行是抢占时间片,如果a++在执行到一半时,CPU时间片被另外一个线程抢占了,这时候就没有原子性,需要给执行操作增加一个锁,类似于地铁站卫生间小门一样,一次只能进一个人。

​ 可见性:在 聊可见性之前,我们先聊一下CPU的缓存机制,CPU为了提高计算的速度,CPU会从主存中复制一份数据到工作内存(有直接使用,没有直接复制份到缓存),计算的时候直接取工作内存中的数据,计算完成后再将工作内存中的数据更新回主存中。当多个线程同时操作一个变量b时,第一个线程变量b未完成从工作内存更新回主存中时,第二个线程已经开始,重新从主存中将b变量复制一份数据到自己工作内存中,此时第一个线程工作内存中的b变量与第二个线程中的b变量已经不一致。这样就造成了数据的不可见性。

​ 指令重排:JVM为优化代码执行效率,在执行中会对代码顺序进行重新排序(不会影响代码的执行结果),这个情况在单线程中不会出现执行结果错误,但是在多线程中会出现错误,指令重排会造成可见性的问题。

public class User {
    static int a = 0;
    static int b = 0;
    static int c = 0;
    static int num = 1000000000;
    static boolean bStatu;

    public static void main(String[] args) {
        Thread read = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < num; i++) {
                    if (bStatu) {
                        System.out.println("结果=" + c);
                        a = 0;
                        b = 0;
                        c = 0;
                        bStatu = false;
                    }
                }
            }
        });
        Thread write = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < num; i++) {
                    a = 1;
                    b = 1;
                    c = a + b;
                    bStatu = true;
                }
            }
        });

        write.start();
        read.start();
    }
}

​ 执行上面这段程序,逻辑上应该全为结果=2,但是执行过程中会出现结果=0的情况,这就是指令重排造成的

5、java有那几种方式创建线程?

public class ThreadMain {
    public static void main(String[] args) {
        /**
         * 方式一
         */
        ThreadTest threadWork = new ThreadTest();
        threadWork.start();

        /**
         * 方式二
         */
        Thread runnableTest = new Thread(new RunnableTest());
        runnableTest.start();

        /**
         * 方式三
         */
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread方式三");
            }
        });
        thread3.start();

        /**
         * 方式四
         */
        FutureTask futureTask = new FutureTask(new CallabeTest());
        Thread thread4 = new Thread(futureTask);
        thread4.start();
        try {
            System.out.println("线程输出返回结果=" + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        /**
         * 方式五
         */
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(new ThreadTest());


    }
}

class ThreadTest extends Thread {

    @Override
    public void run() {
        System.out.println("ThreadTest");
    }
}


class RunnableTest implements Runnable {

    @Override
    public void run() {
        System.out.println("RunnableTest");
    }
}

class CallabeTest implements Callable<String> {

    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        return "线程返回成功啦!";
    }
}