Java面试篇-并发编程

1.进程与线程的区别?

        何为进程,进程可以理解为一个应用,一个app的实例,一般都是单实例进程,也有多实例进程(比如开两个txt文档),其中的线程才是真正执行任务的一个单元。所以进程包含线程,一个进程里面至少有一个线程。同一个进程里面的线程可以共享资源。

2.并行与并发的区别?

        这里要区分单核cpu和多核cpu的情况。

        单核cpu时,其本质上只有并发,以极快的速度交替执行,造成并发的假象。

        多核cpu时,在并发的基础上,才有真正的并行。

        并发:一个线程在一个cpu上交替执行,强调交替执行。

        并行:多个cpu同时处理,强调同时处理一些事,如4核cpu同时执行4线程。

3.线程创建的方式有哪些?

1.继承Thread类,重写run(),然后调用start()开启子线程

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("子线程运行了!");
    }
}

public class MyTest {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread1.start();
        myThread2.start();
    }
}

2.实现Runnable接口,重写run(),然后new一个Thread类,在构造方法中传入Runnable的实现类,最后调用start()开启子线程

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("子线程运行了");
    }
}
public class MyTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.start();
        t2.start();
    }
}

3.实现Callable接口,可以指定泛型来确定返回的类型。调用时,创建一个FutureTask对象,在该构造方法中传入Callable的实现类对象,再new一个Thread对象,在该构造方法中传入FutureTask对象,最后调用start()开启线程。FutureTask对象的get()可以获得线程的返回值。

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "线程返回结果";
    }
}

public class MyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();

        FutureTask<String> futureTask = new FutureTask<>(myCallable);

        Thread t1 = new Thread(futureTask);
        t1.start();

        // 获取线程返回的结果
        String s = futureTask.get();
        System.out.println(s);
    }
}

4.创建线程池。使用Executors.newFixedThreadPool(int nThreads)可以创建一个指定大小的默认线程池,使用该对象调用submit(),参数中传入Runnable的实现类对象,即可创建子线程。

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

public class MyTest {
    public static void main(String[] args) {
        MyExecutors myExecutors = new MyExecutors();

        // 创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        threadPool.submit(myExecutors);

        // 关闭线程池
        threadPool.shutdown();
    }
}

4.实现Runnable与实现Callable的方式创建的线程有什么区别?

1.实现Runnable创建的线程,没有返回值,实现Callable创建的线程有返回值

2.实现Runnable创建的线程,不能抛出异常,实现Callable创建的线程可以抛出异常

5.在启动线程的时候,可以使用run方法吗?run()与start()有什么区别?

可以使用run方法,只不过这时的run方法只是普通方法,不是新开一个线程去执行,还是主线程去执行的。run()作为普通方法可以被多次调用。start()是线程开启的方法,一个线程只能开启一次,再次开启会提示异常。

6.线程包含哪些状态?状态之间是如何变化的?

线程的状态,可查看Thread类中的枚举类:State,共有六种状态,分别对应:新建、可运行、阻塞、等待、计时等待、已终止

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
}

状态之间的变化关系:正常线程执行时,一般是新建(创建线程对象时)->可运行(调用start()时)->已终止(线程执行结束)

但是在可运行阶段,还可能会有三种状态,阻塞、等待、计时等待在这里可能发生。

阻塞:在可运行状态时,无法获得锁,会进入阻塞状态,如果获得锁,会重新切换为可运行状态

等待:在可运行状态时,锁对象调用了wait(),会进入等待状态,如果该锁对象调用了notify(),会重新切换到可运行状态

计时等待:在可运行状态时,线程调用了sleep(时间)方法指定了睡眠时间,会进入计时等待状态,当睡眠时间结束时,会重新切换到可运行状态

7.新建t1、t2、t3三个线程,如何保证它们按照顺序执行?

可以调用线程对象的join(),该方法能阻塞调用此方法的线程,进入timed_waiting状态,只有调用对象执行完毕,才能继续执行。以下是t1、t2、t3依次执行的例子。

public class MyTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> System.out.println("线程1执行"));

        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2执行");
        });

        Thread t3 = new Thread(() -> {
            try {
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3执行");
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

8.notify()和notifyAll()有什么区别?

notify():只随机唤醒一个wait的线程

notifyAll():唤醒所有wait的线程

下列例子展示notify()和notifyAll()的用法:

public class MyTest {

    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock){
                System.out.println(Thread.currentThread().getName() + "...waiting...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "...被唤醒了...");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock){
                System.out.println(Thread.currentThread().getName() + "...waiting...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "...被唤醒了...");
            }
        }, "t2");

        t1.start();
        t2.start();

        Thread.sleep(2000);

        synchronized (lock){
            // 唤醒其中一个线程
            lock.notify();
            // 唤醒所有线程
            // lock.notifyAll();
        }
    }
}

9.wait()、wait(long)与sleep(long)方法的异同点是什么?

相同点:都能使当前线程暂时放弃cpu的使用权,进入等待状态或者计时等待状态。

不同点:

1.方法归属不同:

sleep(long)是Thread类的静态方法,wait()和wait(long)则是Object的成员方法,每个对象都有该方法。

2.醒来时机不同

wait(long)与sleep(long)会在等待时间到了之后自动醒来,wait()和wait(long)可以通过notify()手动唤醒,wait()如果不手动唤醒则会一直等待下去。

3.锁特性不同(重点)

wait方法的调用必须先获取wait对象的锁,而sleep无限制

wait方法执行后会释放对象锁,允许其他线程获取该对象锁,而sleep如果在synchronized代码块中执行,并不会释放对象锁。

10.如何停止一个正在运行的线程?

有三种方式,其中第一种和第三种比较推荐

1.使用退出标志。

2.使用线程的stop()强行终止(不推荐,该方法已废弃)

3.使用线程的interrupt()中断进程:

  • 如果中断了阻塞中(或等待中、计时等待中的)的线程,则线程会抛出InterruptedException
  • 如果中断了正常的线程,不会抛出异常,线程对象的isInterrupted()方法会返回为true

11.说一说synchronized的底层原理

synchronized对象锁是采用互斥的方式让同一时刻最多只有一个线程能持有对象锁。

其底层原理由monitor实现(monitor是jvm级别的对象,由c++实现),线程获得锁需要使用对象锁关联monitor

在monitor内部有三个属性,分别是owner、entryList和waitset。

其中owner是存放正在执行的线程,初始为空,如果有多个线程访问,则会取其中一个线程放入到owner中,剩余线程则会进入到entryList中,被设定了等待时间的线程会进入到waitset中。每次进来都会判断owner是否为空,如果为空,则会从entryList中随机取出一个线程执行。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值